訊息轉換器
AmqpTemplate
也定義了數個用於傳送和接收訊息的方法,這些方法委派給 MessageConverter
。 MessageConverter
為每個方向提供單一方法:一個用於轉換為 Message
,另一個用於從 Message
轉換。 請注意,在轉換為 Message
時,除了物件之外,您還可以提供屬性。 object
參數通常對應於訊息主體。 以下清單顯示 MessageConverter
介面定義
public interface MessageConverter {
Message toMessage(Object object, MessageProperties messageProperties)
throws MessageConversionException;
Object fromMessage(Message message) throws MessageConversionException;
}
AmqpTemplate
上相關的 Message
傳送方法比我們先前討論的方法更簡單,因為它們不需要 Message
實例。 相反地,MessageConverter
負責「建立」每個 Message
,方法是將提供的物件轉換為 Message
主體的位元組陣列,然後新增任何提供的 MessageProperties
。 以下清單顯示各種方法的定義
void convertAndSend(Object message) throws AmqpException;
void convertAndSend(String routingKey, Object message) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message)
throws AmqpException;
void convertAndSend(Object message, MessagePostProcessor messagePostProcessor)
throws AmqpException;
void convertAndSend(String routingKey, Object message,
MessagePostProcessor messagePostProcessor) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message,
MessagePostProcessor messagePostProcessor) throws AmqpException;
在接收端,只有兩種方法:一種接受佇列名稱,另一種依賴於範本的「queue」屬性已設定。 以下清單顯示這兩種方法的定義
Object receiveAndConvert() throws AmqpException;
Object receiveAndConvert(String queueName) throws AmqpException;
在 非同步消費者 中提及的 MessageListenerAdapter 也使用 MessageConverter 。 |
SimpleMessageConverter
MessageConverter
策略的預設實作稱為 SimpleMessageConverter
。 如果您未明確設定替代方案,則這是 RabbitTemplate
實例使用的轉換器。 它處理基於文字的內容、序列化的 Java 物件和位元組陣列。
從 Message
轉換
如果輸入 Message
的內容類型以 "text" 開頭 (例如,"text/plain"),它也會檢查 content-encoding 屬性,以判斷將 Message
主體位元組陣列轉換為 Java String
時要使用的字元集。 如果輸入 Message
上未設定 content-encoding 屬性,則預設會使用 UTF-8 字元集。 如果您需要覆寫該預設設定,您可以設定 SimpleMessageConverter
的實例,設定其 defaultCharset
屬性,並將其注入到 RabbitTemplate
實例中。
如果輸入 Message
的 content-type 屬性值設定為 "application/x-java-serialized-object",則 SimpleMessageConverter
會嘗試將位元組陣列反序列化 (重新水合) 為 Java 物件。 雖然這對於簡單的原型設計可能很有用,但我們不建議依賴 Java 序列化,因為它會導致生產者和消費者之間緊密的耦合。 當然,它也排除了在任一方使用非 Java 系統。 由於 AMQP 是一種線路級協定,因此如果因為這些限制而失去大部分優勢,那將是不幸的。 在接下來的兩節中,我們將探索一些替代方案,用於傳遞豐富的網域物件內容,而無需依賴 Java 序列化。
對於所有其他內容類型,SimpleMessageConverter
會直接將 Message
主體內容作為位元組陣列傳回。
請參閱 Java 反序列化 以取得重要資訊。
SerializerMessageConverter
此轉換器與 SimpleMessageConverter
類似,不同之處在於它可以設定其他 Spring Framework Serializer
和 Deserializer
實作,以用於 application/x-java-serialized-object
轉換。
請參閱 Java 反序列化 以取得重要資訊。
Jackson2JsonMessageConverter
本節涵蓋如何使用 Jackson2JsonMessageConverter
來回轉換 Message
。 它包含以下章節
轉換為 Message
如上一節所述,通常不建議依賴 Java 序列化。 一種更常見的替代方案是 JSON (JavaScript Object Notation),它在不同的語言和平台之間更具彈性和可攜性。 可以在任何 RabbitTemplate
實例上設定轉換器,以覆寫其 SimpleMessageConverter
預設值的使用。 Jackson2JsonMessageConverter
使用 com.fasterxml.jackson
2.x 程式庫。 以下範例設定 Jackson2JsonMessageConverter
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
<!-- if necessary, override the DefaultClassMapper -->
<property name="classMapper" ref="customClassMapper"/>
</bean>
</property>
</bean>
如上所示,Jackson2JsonMessageConverter
預設使用 DefaultClassMapper
。 型別資訊會新增至 (和從) MessageProperties
擷取。 如果輸入訊息在 MessageProperties
中不包含型別資訊,但您知道預期的型別,則可以使用 defaultType
屬性來設定靜態型別,如下列範例所示
<bean id="jsonConverterWithDefaultType"
class="o.s.amqp.support.converter.Jackson2JsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="thing1.PurchaseOrder"/>
</bean>
</property>
</bean>
此外,您可以提供從 TypeId
標頭中的值到自訂對應。 以下範例示範如何執行此操作
@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
@Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<>();
idClassMapping.put("thing1", Thing1.class);
idClassMapping.put("thing2", Thing2.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
現在,如果傳送系統將標頭設定為 thing1
,則轉換器會建立 Thing1
物件,依此類推。 請參閱 從非 Spring 應用程式接收 JSON 範例應用程式,以取得關於從非 Spring 應用程式轉換訊息的完整討論。
從 2.4.3 版開始,如果 supportedMediaType
具有 charset
參數,則轉換器不會新增 contentEncoding
訊息屬性;這也用於編碼。 已新增一個新的方法 setSupportedMediaType
String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));
從 Message
轉換
輸入訊息會根據傳送系統新增至標頭的型別資訊轉換為物件。
從 2.4.3 版開始,如果沒有 contentEncoding
訊息屬性,轉換器會嘗試偵測 contentType
訊息屬性中的 charset
參數並使用它。 如果兩者都不存在,如果 supportedMediaType
具有 charset
參數,它將用於解碼,最後會退回到 defaultCharset
屬性。 已新增一個新的方法 setSupportedMediaType
String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));
在 1.6 之前的版本中,如果型別資訊不存在,轉換將會失敗。 從 1.6 版開始,如果型別資訊遺失,轉換器會使用 Jackson 預設值 (通常是 Map) 轉換 JSON。
此外,從 1.6 版開始,當您 (在方法上) 使用 @RabbitListener
註解時,推斷的型別資訊會新增至 MessageProperties
。 這可讓轉換器轉換為目標方法的引數型別。 這僅適用於有一個沒有註解的參數或一個帶有 @Payload
註解的參數。 在分析期間,會忽略 Message
型別的參數。
預設情況下,推斷的型別資訊將覆寫輸入 TypeId 和傳送系統建立的相關標頭。 這可讓接收系統自動轉換為不同的網域物件。 這僅適用於參數型別是具體的 (非抽象或介面) 或來自 java.util 套件。 在所有其他情況下,都會使用 TypeId 和相關標頭。 在某些情況下,您可能希望覆寫預設行為,並始終使用 TypeId 資訊。 例如,假設您有一個 @RabbitListener 採用 Thing1 引數,但訊息包含 Thing2 ,它是 Thing1 的子類別 (它是具體的)。 推斷的型別將不正確。 若要處理這種情況,請將 Jackson2JsonMessageConverter 上的 TypePrecedence 屬性設定為 TYPE_ID ,而不是預設的 INFERRED 。 (該屬性實際上位於轉換器的 DefaultJackson2JavaTypeMapper 上,但在轉換器上提供了一個 setter 以方便使用。) 如果您注入自訂型別對應器,則應改為在對應器上設定該屬性。 |
當從 Message 轉換時,傳入的 MessageProperties.getContentType() 必須符合 JSON 標準 (contentType.contains("json") 用於檢查)。 從 2.2 版開始,如果沒有 contentType 屬性,或它具有預設值 application/octet-stream ,則假定為 application/json 。 若要回復為先前的行為 (傳回未轉換的 byte[] ),請將轉換器的 assumeSupportedContentType 屬性設定為 false 。 如果內容類型不受支援,則會發出 WARN 記錄訊息 Could not convert incoming message with content-type […] ,並將 message.getBody() 原樣傳回 — 作為 byte[] 。 因此,為了滿足消費者端的 Jackson2JsonMessageConverter 需求,生產者必須新增 contentType 訊息屬性 — 例如,作為 application/json 或 text/x-json ,或使用 Jackson2JsonMessageConverter ,它會自動設定標頭。 以下清單顯示一些轉換器呼叫 |
@RabbitListener
public void thing1(Thing1 thing1) {...}
@RabbitListener
public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<Foo> message) {...}
@RabbitListener
public void thing1(Thing1 thing1, String bar) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<?> message) {...}
在前面清單中的前四個案例中,轉換器會嘗試轉換為 Thing1
型別。 第五個範例無效,因為我們無法判斷哪個引數應接收訊息酬載。 在第六個範例中,由於泛型型別是 WildcardType
,因此套用 Jackson 預設值。
但是,您可以建立自訂轉換器並使用 targetMethod
訊息屬性來決定要將 JSON 轉換為哪種類型。
只有在方法層級宣告 @RabbitListener 註解時,才能實現此型別推斷。 使用類別層級 @RabbitListener 時,轉換後的型別用於選取要叫用的 @RabbitHandler 方法。 因此,基礎架構提供 targetObject 訊息屬性,您可以在自訂轉換器中使用它來判斷型別。 |
從 1.6.11 版開始,Jackson2JsonMessageConverter ,因此,DefaultJackson2JavaTypeMapper (DefaultClassMapper ) 提供 trustedPackages 選項以克服 Serialization Gadgets 漏洞。 預設情況下,為了向後相容性,Jackson2JsonMessageConverter 信任所有套件 — 也就是說,它使用 * 作為選項。 |
從 2.4.7 版開始,如果 Jackson 在還原序列化訊息主體後傳回 null
,則可以將轉換器設定為傳回 Optional.empty()
。 這有助於 @RabbitListener
以兩種方式接收 null 酬載
@RabbitListener(queues = "op.1")
void listen(@Payload(required = false) Thing payload) {
handleOptional(payload); // payload might be null
}
@RabbitListener(queues = "op.2")
void listen(Optional<Thing> optional) {
handleOptional(optional.orElse(this.emptyThing));
}
若要啟用此功能,請將 setNullAsOptionalEmpty
設定為 true
;當 false
(預設值) 時,轉換器會回復為原始訊息主體 (byte[]
)。
@Bean
Jackson2JsonMessageConverter converter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setNullAsOptionalEmpty(true);
return converter;
}
還原序列化抽象類別
在 2.2.8 之前的版本中,如果 @RabbitListener
的推斷型別是抽象類別 (包括介面),則轉換器會退回到在標頭中尋找型別資訊,如果存在,則使用該資訊;如果不存在,它會嘗試建立抽象類別。 當使用自訂 ObjectMapper
(已設定自訂還原序列化器以處理抽象類別) 時,但傳入訊息具有無效的型別標頭時,這會導致問題。
從 2.2.8 版開始,預設情況下會保留先前的行為。 如果您有此類自訂 ObjectMapper
,並且想要忽略型別標頭,並始終使用推斷的型別進行轉換,請將 alwaysConvertToInferredType
設定為 true
。 為了向後相容性,並避免在轉換失敗時 (使用標準 ObjectMapper
) 嘗試轉換的額外負荷,這是必要的。
使用 Spring Data Projection 介面
從 2.2 版開始,您可以將 JSON 轉換為 Spring Data Projection 介面,而不是具體型別。 這允許非常選擇性且低耦合的資料繫結,包括從 JSON 文件內的多個位置查閱值。 例如,以下介面可以定義為訊息酬載型別
interface SomeSample {
@JsonPath({ "$.username", "$.user.name" })
String getUsername();
}
@RabbitListener(queues = "projection")
public void projection(SomeSample in) {
String username = in.getUsername();
...
}
預設情況下,存取器方法將用於查閱接收到的 JSON 文件中作為欄位的屬性名稱。 @JsonPath
運算式允許自訂值查閱,甚至定義多個 JSON 路徑運算式,以從多個位置查閱值,直到運算式傳回實際值。
若要啟用此功能,請在訊息轉換器上將 useProjectionForInterfaces
設定為 true
。 您也必須將 spring-data:spring-data-commons
和 com.jayway.jsonpath:json-path
新增至類別路徑。
當用作 @RabbitListener
方法的參數時,介面型別會自動正常傳遞至轉換器。
使用 RabbitTemplate
從 Message
轉換
如先前所述,型別資訊在訊息標頭中傳達,以協助轉換器從訊息轉換時。 這在大多數情況下都能正常運作。 但是,當使用泛型型別時,它只能轉換簡單物件和已知的「容器」物件 (清單、陣列和 Map)。 從 2.0 版開始,Jackson2JsonMessageConverter
實作 SmartMessageConverter
,這使其可以與採用 ParameterizedTypeReference
引數的新 RabbitTemplate
方法一起使用。 這允許轉換複雜的泛型型別,如下列範例所示
Thing1<Thing2<Cat, Hat>> thing1 =
rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
從 2.1 版開始,已移除 AbstractJsonMessageConverter 類別。 它不再是 Jackson2JsonMessageConverter 的基底類別。 它已由 AbstractJackson2MessageConverter 取代。 |
MarshallingMessageConverter
另一個選項是 MarshallingMessageConverter
。 它委派給 Spring OXM 程式庫的 Marshaller
和 Unmarshaller
策略介面的實作。 您可以在 這裡 閱讀有關該程式庫的更多資訊。 就設定而言,最常見的做法是僅提供建構函式引數,因為 Marshaller
的大多數實作也實作 Unmarshaller
。 以下範例示範如何設定 MarshallingMessageConverter
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.MarshallingMessageConverter">
<constructor-arg ref="someImplemenationOfMarshallerAndUnmarshaller"/>
</bean>
</property>
</bean>
Jackson2XmlMessageConverter
此類別在 2.1 版中引入,可用於來回轉換 XML 訊息。
Jackson2XmlMessageConverter
和 Jackson2JsonMessageConverter
都具有相同的基底類別:AbstractJackson2MessageConverter
。
引入 AbstractJackson2MessageConverter 類別是為了取代已移除的類別:AbstractJsonMessageConverter 。 |
Jackson2XmlMessageConverter
使用 com.fasterxml.jackson
2.x 程式庫。
您可以使用它,方式與 Jackson2JsonMessageConverter
相同,但它支援 XML 而不是 JSON。 以下範例設定 Jackson2JsonMessageConverter
<bean id="xmlConverterWithDefaultType"
class="org.springframework.amqp.support.converter.Jackson2XmlMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="foo.PurchaseOrder"/>
</bean>
</property>
</bean>
請參閱 Jackson2JsonMessageConverter 以取得更多資訊。
從 2.2 版開始,如果沒有 contentType 屬性,或它具有預設值 application/octet-stream ,則假定為 application/xml 。 若要回復為先前的行為 (傳回未轉換的 byte[] ),請將轉換器的 assumeSupportedContentType 屬性設定為 false 。 |
ContentTypeDelegatingMessageConverter
此類別在 1.4.2 版中引入,允許根據 MessageProperties
中的內容類型屬性委派給特定的 MessageConverter
。 預設情況下,如果沒有 contentType
屬性,或存在與任何已設定的轉換器都不符的值,則它會委派給 SimpleMessageConverter
。 以下範例設定 ContentTypeDelegatingMessageConverter
<bean id="contentTypeConverter" class="ContentTypeDelegatingMessageConverter">
<property name="delegates">
<map>
<entry key="application/json" value-ref="jsonMessageConverter" />
<entry key="application/xml" value-ref="xmlMessageConverter" />
</map>
</property>
</bean>
Java 反序列化
本節涵蓋如何還原序列化 Java 物件。
從不受信任的來源還原序列化 Java 物件時,可能存在漏洞。 如果您接受來自不受信任來源且 預設情況下,允許清單為空,表示不會還原序列化任何類別。 您可以設定模式清單,例如 會依序檢查模式,直到找到相符項為止。 如果沒有相符項,則會擲回 您可以使用這些轉換器上的 |
訊息屬性轉換器
MessagePropertiesConverter
策略介面用於在 Rabbit Client BasicProperties
和 Spring AMQP MessageProperties
之間進行轉換。 預設實作 (DefaultMessagePropertiesConverter
) 通常足以滿足大多數用途,但如果需要,您可以實作自己的實作。 當大小不大於 1024
位元組時,預設屬性轉換器會將 LongString
型別的 BasicProperties
元素轉換為 String
實例。 較大的 LongString
實例不會轉換 (請參閱下一段)。 此限制可以使用建構函式引數覆寫。
從 1.6 版開始,DefaultMessagePropertiesConverter
預設現在將長度超過長字串限制 (預設值:1024) 的標頭保留為 LongString
實例。 您可以透過 getBytes[]
、toString()
或 getStream()
方法存取內容。
先前,DefaultMessagePropertiesConverter
將此類標頭「轉換」為 DataInputStream
(實際上,它只是參考 LongString
實例的 DataInputStream
)。 在輸出時,此標頭未轉換 (除非轉換為字串 — 例如,透過在串流上呼叫 toString()
的 java.io.DataInputStream@1d057a39
)。
現在,大型輸入 LongString
標頭在輸出時也已正確「轉換」(預設情況下)。
提供了一個新的建構函式,可讓您設定轉換器以像以前一樣工作。 以下清單顯示方法的 Javadoc 註解和宣告
/**
* Construct an instance where LongStrings will be returned
* unconverted or as a java.io.DataInputStream when longer than this limit.
* Use this constructor with 'true' to restore pre-1.6 behavior.
* @param longStringLimit the limit.
* @param convertLongLongStrings LongString when false,
* DataInputStream when true.
* @since 1.6
*/
public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... }
同樣從 1.6 版開始,已將名為 correlationIdString
的新屬性新增至 MessageProperties
。 先前,在來回轉換 RabbitMQ 用戶端使用的 BasicProperties
時,執行了不必要的 byte[] <→ String
轉換,因為 MessageProperties.correlationId
是 byte[]
,但 BasicProperties
使用 String
。 (最終,RabbitMQ 用戶端使用 UTF-8 將 String
轉換為位元組,以放入協定訊息中)。
為了提供最大的向後相容性,已將名為 correlationIdPolicy
的新屬性新增至 DefaultMessagePropertiesConverter
。 這採用 DefaultMessagePropertiesConverter.CorrelationIdPolicy
列舉引數。 預設情況下,它設定為 BYTES
,這會複製先前的行為。
對於輸入訊息
-
STRING
:僅對應correlationIdString
屬性 -
BYTES
:僅對應correlationId
屬性 -
BOTH
:對應兩個屬性
對於輸出訊息
-
STRING
:僅對應correlationIdString
屬性 -
BYTES
:僅對應correlationId
屬性 -
BOTH
:同時考慮兩個屬性,其中String
屬性優先
同樣從 1.6 版開始,輸入 deliveryMode
屬性不再對應到 MessageProperties.deliveryMode
。 它改為對應到 MessageProperties.receivedDeliveryMode
。 此外,輸入 userId
屬性不再對應到 MessageProperties.userId
。 它改為對應到 MessageProperties.receivedUserId
。 這些變更是為了避免在將相同的 MessageProperties
物件用於輸出訊息時,意外傳播這些屬性。
從 2.2 版開始,DefaultMessagePropertiesConverter
使用 getName()
而不是 toString()
轉換任何具有 Class<?>
型別值的自訂標頭;這避免了使用應用程式必須從 toString()
表示法中剖析類別名稱。 對於滾動升級,您可能需要變更您的消費者以了解兩種格式,直到所有生產者都升級。