メッセージコンバーター

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 デシリアライゼーションを参照してください。

Message への変換

任意の Java オブジェクトから Message に変換する場合、SimpleMessageConverter は同様にバイト配列、文字列、シリアライズ可能なインスタンスを処理します。これらをそれぞれバイトに変換し (バイト配列の場合、変換するものは何もありません)、それに応じて content-type プロパティを設定します。変換する Object がこれらの型のいずれとも一致しない場合、Message 本体は null です。

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 以降、Jackson2JsonMessageConverterDefaultJackson2JavaTypeMapper (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 オブジェクトを逆直列化するときに脆弱性が生じる可能性があります。

content-type が application/x-java-serialized-object の信頼できないソースからのメッセージを受け入れる場合は、デシリアライズを許可するパッケージとクラスを構成することを検討する必要があります。これは、DefaultDeserializer を使用するように構成されている場合、SimpleMessageConverter と SerializerMessageConverter の両方に適用されます。

デフォルトでは、許可リストは空です。つまり、クラスは逆直列化されません。

thing1.thing1.thing2.Cat.MySafeClass などのパターンのリストを設定できます。

パターンは、一致が見つかるまで順番にチェックされます。一致しない場合は、SecurityException がスローされます。

これらのコンバーターの allowedListPatterns プロパティを使用してパターンを設定できます。あるいは、すべてのメッセージ発信者を信頼する場合は、環境変数 SPRING_AMQP_DESERIALIZATION_TRUST_ALL またはシステムプロパティ spring.amqp.deserialization.trust.all を true に設定できます。

メッセージプロパティコンバーター

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 に設定されています。

受信メッセージの場合:

  • STRINGcorrelationIdString プロパティのみがマップされます

  • BYTEScorrelationId プロパティのみがマップされます

  • BOTH: 両方のプロパティがマップされています

送信メッセージの場合:

  • STRINGcorrelationIdString プロパティのみがマップされます

  • BYTEScorrelationId プロパティのみがマップされます

  • BOTH: 両方のプロパティが考慮され、String プロパティが優先されます

また、バージョン 1.6 以降、受信 deliveryMode プロパティは MessageProperties.deliveryMode にマップされなくなりました。代わりに MessageProperties.receivedDeliveryMode にマップされます。また、受信 userId プロパティは MessageProperties.userId にマップされなくなりました。代わりに MessageProperties.receivedUserId にマップされます。これらの変更は、送信メッセージに同じ MessageProperties オブジェクトが使用されている場合に、これらのプロパティが予期せず伝播されるのを防ぐためのものです。

バージョン 2.2 以降、DefaultMessagePropertiesConverter は、toString() の代わりに getName() を使用して、型 Class<?> の値を持つカスタムヘッダーを変換します。これにより、アプリケーションが toString() 表現からクラス名を解析する必要がなくなります。ローリングアップグレードの場合、すべてのプロデューサーがアップグレードされるまで、両方の形式を理解するためにコンシューマーを変更する必要がある場合があります。