メッセージコンバーター
AmqpTemplate
は、MessageConverter
に委譲するメッセージを送受信するためのいくつかのメソッドも定義します。MessageConverter
は、方向ごとに 1 つの方法を提供します。1 つは Message
への変換用で、もう 1 つは Message
からの変換用です。Message
に変換する場合、オブジェクトに加えてプロパティも提供できることに注意してください。object
パラメーターは通常、メッセージ本文に対応します。次のリストは、MessageConverter
インターフェース定義を示しています。
public interface MessageConverter {
Message toMessage(Object object, MessageProperties messageProperties)
throws MessageConversionException;
Object fromMessage(Message message) throws MessageConversionException;
}
関連する AmqpTemplate
上の Message
-sending メソッドは、Message
インスタンスを必要としないため、以前に説明したメソッドよりも単純です。代わりに、MessageConverter
は、提供されたオブジェクトを Message
本体のバイト配列に変換し、提供された MessageProperties
を追加することによって、各 Message
を「作成」する責任があります。次のリストは、さまざまなメソッドの定義を示しています。
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;
受信側には、キュー名を受け入れる方法と、設定されているテンプレートの「キュー」プロパティに依存する方法の 2 つの方法しかありません。次のリストは、2 つのメソッドの定義を示しています。
Object receiveAndConvert() throws AmqpException;
Object receiveAndConvert(String queueName) throws AmqpException;
非同期コンシューマーでメンションされている MessageListenerAdapter も MessageConverter を使用しています。 |
SimpleMessageConverter
MessageConverter
戦略のデフォルトの実装は SimpleMessageConverter
と呼ばれます。これは、代替を明示的に構成しない場合に RabbitTemplate
のインスタンスによって使用されるコンバーターです。テキストベースのコンテンツ、直列化された Java オブジェクト、およびバイト配列を処理します。
Message
からの変換
入力 Message
のコンテンツ型が「テキスト」で始まる場合 ("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 はワイヤレベルのプロトコルであるため、このような制限によってその利点の多くが失われるのは残念なことです。次の 2 つのセクションでは、Java シリアライゼーションに依存せずに豊富なドメインオブジェクトコンテンツを渡すためのいくつかの代替メソッドを検討します。
他のすべてのコンテンツ型の場合、SimpleMessageConverter
は Message
本文のコンテンツをバイト配列として直接返します。
重要な情報については、Java デシリアライゼーションを参照してください。
SerializerMessageConverter
このコンバーターは、application/x-java-serialized-object
変換用の他の Spring Framework Serializer
および Deserializer
実装で構成できることを除いて、SimpleMessageConverter
に似ています。
重要な情報については、Java デシリアライゼーションを参照してください。
Jackson2JsonMessageConverter
このセクションでは、Jackson2JsonMessageConverter
を使用して Message
との間で変換する方法について説明します。次のセクションがあります。
Message
への改造
前のセクションで記述されていたように、Java シリアライゼーションに依存することは一般的に推奨されません。さまざまな言語やプラットフォーム間でより柔軟で移植性の高い一般的な代替手段の 1 つは、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 以外のアプリケーションからのメッセージの変換に関する完全な説明については、Spring 以外のアプリケーションからの JSON の受信サンプルアプリケーションを参照してください。
バージョン 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 の既定値 (通常はマップ) を使用して JSON を変換します。
また、バージョン 1.6 以降、(メソッドで) @RabbitListener
アノテーションを使用すると、推測された型情報が MessageProperties
に追加されます。これにより、コンバーターはターゲットメソッドの引数の型に変換できます。これは、アノテーションのないパラメーターが 1 つある場合、または @Payload
アノテーション付きのパラメーターが 1 つある場合にのみ適用されます。型 Message
のパラメーターは、分析中に無視されます。
デフォルトでは、推測された型情報は、送信システムによって作成された受信 TypeId および関連ヘッダーをオーバーライドします。これにより、受信システムは自動的に別のドメインオブジェクトに変換されます。これは、パラメーターの型が具体的 (抽象的またはインターフェースではない) であるか、java.util パッケージからのものである場合にのみ適用されます。それ以外の場合はすべて、TypeId および関連するヘッダーが使用されます。デフォルトの動作をオーバーライドして、常に TypeId 情報を使用したい場合があります。例: Thing1 引数を取る @RabbitListener があるが、メッセージには Thing1 のサブクラス (具象) である Thing2 が含まれているとします。推測された型は正しくありません。この状況に対処するには、Jackson2JsonMessageConverter の TypePrecedence プロパティをデフォルトの INFERRED ではなく TYPE_ID に設定します。(プロパティは実際にはコンバーターの 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) {...}
前のリストの最初の 4 つのケースでは、コンバーターは Thing1
型への変換を試みます。5 番目の例は無効です。なぜなら、どの引数がメッセージペイロードを受け取るべきかを判断できないからです。6 番目の例では、ジェネリクス型が WildcardType
であるため、Jackson のデフォルトが適用されます。
ただし、カスタムコンバーターを作成し、targetMethod
メッセージプロパティを使用して、JSON をどの型に変換するかを決定することができます。
この型の推論は、@RabbitListener アノテーションがメソッドレベルで宣言されている場合にのみ実現できます。クラスレベルの @RabbitListener では、変換された型を使用して、呼び出す @RabbitHandler メソッドを選択します。このため、インフラストラクチャには targetObject メッセージプロパティが用意されており、これをカスタムコンバーターで使用して型を判別できます。 |
バージョン 1.6.11 以降、Jackson2JsonMessageConverter 、DefaultJackson2JavaTypeMapper (DefaultClassMapper ) は、連載ガジェット 脆弱性を克服するための trustedPackages オプションを提供します。デフォルトでは、下位互換性のために、Jackson2JsonMessageConverter はすべてのパッケージを信頼します。つまり、オプションに * を使用します。 |
バージョン 2.4.7 以降では、メッセージ本文を逆直列化した後に Jackson が null
を返す場合、コンバーターは Optional.empty()
を返すように構成できます。これにより、次の 2 つの方法で @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 射影 インターフェースの使用
バージョン 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
からの変換
前述のように、型情報は、メッセージから変換するときにコンバーターを支援するために、メッセージヘッダーで伝達されます。ほとんどの場合、これで問題なく動作します。ただし、ジェネリクス型を使用する場合は、単純なオブジェクトと既知の「コンテナー」オブジェクト (リスト、配列、マップ) のみを変換できます。バージョン 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 ライブラリを使用します。
JSON の代わりに XML をサポートする点を除いて、Jackson2JsonMessageConverter
と同じように使用できます。次の例では、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 クライアント BasicProperties
と Spring AMQP MessageProperties
の間の変換に使用されます。ほとんどの場合、デフォルトの実装 (DefaultMessagePropertiesConverter
) で十分ですが、必要に応じて独自のものを実装できます。デフォルトのプロパティコンバーターは、サイズが 1024
バイト以下の場合、型 LongString
の BasicProperties
エレメントを String
インスタンスに変換します。それより大きい LongString
インスタンスは変換されません (次の段落を参照)。この制限は、コンストラクター引数でオーバーライドできます。
バージョン 1.6 以降、長い文字列の制限 (デフォルト: 1024) よりも長いヘッダーは、デフォルトで DefaultMessagePropertiesConverter
によって 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
との間で変換するときに、MessageProperties.correlationId
は byte[]
であるが、BasicProperties
は String
を使用するため、不要な byte[] <→ 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
は、toString()
の代わりに getName()
を使用して、型 Class<?>
の値を持つカスタムヘッダーを変換します。これにより、アプリケーションが toString()
表現からクラス名を解析する必要がなくなります。ローリングアップグレードの場合、すべてのプロデューサーがアップグレードされるまで、両方の形式を理解するためにコンシューマーを変更する必要がある場合があります。