メッセージコンバーター
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 デシリアライゼーションを参照してください。
JacksonJsonMessageConverter
このセクションでは、JacksonJsonMessageConverter を使用して Message との間で変換する方法について説明します。次のセクションがあります。
AbstractJackson2MessageConverter、その実装、関連する Jackson2JavaTypeMapper API は、4.0 バージョンで削除され、Jackson 3 に基づく各クラスに置き換えられるため、非推奨となりました。それぞれの移行ガイドについては、非推奨クラスのうち JavaDocs を参照してください。 |
Message への改造
前のセクションで記述されていたように、Java の直列化に依存することは一般的に推奨されません。より柔軟で、異なる言語やプラットフォーム間で移植性の高い、比較的一般的な代替手段の一つが JSON (JavaScript オブジェクト記法) です。コンバーターは、任意の RabbitTemplate インスタンスで設定することで、SimpleMessageConverter のデフォルト設定をオーバーライドできます。JacksonJsonMessageConverter は Jackson 3.x ライブラリを使用します。次の例は、JacksonJsonMessageConverter の設定例です。
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.JacksonJsonMessageConverter">
<!-- if necessary, override the DefaultClassMapper -->
<property name="classMapper" ref="customClassMapper"/>
</bean>
</property>
</bean> 上記のように、JacksonJsonMessageConverter はデフォルトで DefaultClassMapper を使用します。型情報は MessageProperties に追加 (および取得) されます。受信メッセージに MessageProperties の型情報が含まれていないが、予想される型がわかっている場合は、次の例に示すように、defaultType プロパティを使用して静的型を構成できます。
<bean id="jsonConverterWithDefaultType"
class="o.s.amqp.support.converter.JacksonJsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="thing1.PurchaseOrder"/>
</bean>
</property>
</bean> さらに、TypeId ヘッダーの値からカスタムマッピングを提供できます。次の例は、その方法を示しています。
@Bean
public JacksonJsonMessageConverter jsonMessageConverter() {
JacksonJsonMessageConverter jsonConverter = new JacksonJsonMessageConverter();
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 が含まれているとします。推論された型は正しくありません。この状況に対処するには、JacksonJsonMessageConverter の TypePrecedence プロパティを、デフォルトの INFERRED ではなく TYPE_ID に設定します。(プロパティは実際にはコンバーターの DefaultJacksonJavaTypeMapper にありますが、便宜上コンバーターに 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[] として) 返されます。コンシューマー側で JacksonJsonMessageConverter 要件を満たすには、プロデューサーは contentType メッセージプロパティを追加する必要があります。たとえば、application/json または text/x-json として追加するか、ヘッダーを自動的に設定する JacksonJsonMessageConverter を使用します。次のリストは、コンバーター呼び出しの数を示しています。 |
@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 以降、JacksonJsonMessageConverter、DefaultJacksonJavaTypeMapper (DefaultClassMapper)は、連載ガジェット 脆弱性を克服するための trustedPackages オプションを提供します。デフォルトでは、後方互換性のため、JacksonJsonMessageConverter はすべてのパッケージを信頼します。つまり、このオプションには * を使用します。 |
バージョン 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
JacksonJsonMessageConverter converter() {
JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter();
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 から、JacksonJsonMessageConverter は SmartMessageConverter を実装します。これにより、ParameterizedTypeReference 引数を取る新しい RabbitTemplate メソッドで使用できます。これにより、次の例に示すように、複雑なジェネリクス型の変換が可能になります。
Thing1<Thing2<Cat, Hat>> thing1 =
rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });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="someImplementationOfMarshallerAndUnmarshaller"/>
</bean>
</property>
</bean>JacksonXmlMessageConverter
このクラスはバージョン 2.1 で導入され、メッセージを XML との間で変換するために使用できます。
JacksonXmlMessageConverter と JacksonJsonMessageConverter はどちらも同じ基本クラス AbstractJacksonMessageConverter を持っています。
JacksonXmlMessageConverter は Jackson 3.x ライブラリを使用します。
JSON の代わりに XML をサポートする点を除いて、JacksonJsonMessageConverter と同じように使用できます。次の例では、JacksonJsonMessageConverter を構成しています。
<bean id="xmlConverterWithDefaultType"
class="org.springframework.amqp.support.converter.JacksonXmlMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="foo.PurchaseOrder"/>
</bean>
</property>
</bean>詳細については、JacksonJsonMessageConverter を参照してください。
バージョン 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 をバイトに変換し、プロトコルメッセージに入れます)。
後方互換性を最大限に確保するため、DefaultMessagePropertiesConverter に correlationIdPolicy という新しいプロパティが追加されました。このプロパティは 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() 表現からクラス名を解析する必要がなくなります。ローリングアップグレードの場合、すべてのプロデューサーがアップグレードされるまで、両方の形式を理解するためにコンシューマーを変更する必要がある場合があります。