web-dev-qa-db-ja.com

Spring Boot rabbitmq MappingJackson2MessageConverterカスタムオブジェクト変換

私は、rabbitmq交換/キューへのメッセージを「生成」するスプリングブートと、これらのメッセージを「消費」する別のサンプルスプリングブートアプリを含む、シンプルなスプリングブートアプリを作成しようとしています。だから私は2つのアプリ(またはあなたが望むならマイクロサービス)を持っています。 1)「プロデューサー」マイクロサービス2)「コンシューマー」マイクロサービス

「プロデューサー」には2つのドメインオブジェクトがあります。 jooに変換してrabbitmqに送信するFooとBar。 「コンシューマー」は、jsonメッセージを受信して​​、それぞれドメインFooおよびBarに変換する必要があります。どういうわけか、私はこの単純な仕事をすることができません。これに関する例はあまりありません。メッセージコンバーターとしてorg.springframework.messaging.converter.MappingJackson2MessageConverterを使用したい

ここに私がこれまでに持っているものがあります:

プロデューサーマイクロサービス

package demo.producer;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.stereotype.Service;

@SpringBootApplication
public class ProducerApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class, args);
    }

    @Bean
    Queue queue() {
        return new Queue("queue", false);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("exchange");
    }

    @Bean
    Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("queue");
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    @Autowired
    private Sender sender;

    @Override
    public void run(String... args) throws Exception {
        sender.sendToRabbitmq(new Foo(), new Bar());
    }
}

@Service
class Sender {

    @Autowired
    private RabbitMessagingTemplate rabbitMessagingTemplate;
    @Autowired
    private MappingJackson2MessageConverter mappingJackson2MessageConverter;

    public void sendToRabbitmq(final Foo foo, final Bar bar) {

        this.rabbitMessagingTemplate.setMessageConverter(this.mappingJackson2MessageConverter);

        this.rabbitMessagingTemplate.convertAndSend("exchange", "queue", foo);
        this.rabbitMessagingTemplate.convertAndSend("exchange", "queue", bar);

    }
}

class Bar {
    public int age = 33;
}

class Foo {
    public String name = "gustavo";
}

消費者向けマイクロサービス

package demo.consumer;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Service;

@SpringBootApplication
@EnableRabbit
public class ConsumerApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Autowired
    private Receiver receiver;

    @Override
    public void run(String... args) throws Exception {

    }

}

@Service
class Receiver {
    @RabbitListener(queues = "queue")
    public void receiveMessage(Foo foo) {
        System.out.println("Received <" + foo.name + ">");
    }

    @RabbitListener(queues = "queue")
    public void receiveMessage(Bar bar) {
        System.out.println("Received <" + bar.age + ">");
    }
}

class Foo {
    public String name;
}

class Bar {
    public int age;
}

そしてここに私が得ている例外があります:

    org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method could not be invoked with the incoming message
Endpoint handler details:
Method [public void demo.consumer.Receiver.receiveMessage(demo.consumer.Bar)]
Bean [demo.consumer.Receiver@1672fe87]
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.Java:116)
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.Java:93)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.Java:756)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.Java:679)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.Java:83)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.Java:170)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.Java:1257)
    at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.Java:660)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.Java:1021)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.Java:1005)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.Java:83)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.Java:1119)
    at Java.lang.Thread.run(Thread.Java:745)
Caused by: org.springframework.amqp.support.converter.MessageConversionException: Cannot handle message
    ... 13 common frames omitted
Caused by: org.springframework.messaging.converter.MessageConversionException: No converter found to convert to class demo.consumer.Bar, message=GenericMessage [payload=byte[10], headers={amqp_receivedRoutingKey=queue, amqp_receivedExchange=exchange, amqp_deliveryTag=1, amqp_deliveryMode=PERSISTENT, amqp_consumerQueue=queue, amqp_redelivered=false, id=87cf7e06-a78a-ddc1-71f5-c55066b46b11, amqp_consumerTag=amq.ctag-msWSwB4bYGWVO2diWSAHlw, contentType=application/json;charset=UTF-8, timestamp=1433989934574}]
    at org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver.resolveArgument(PayloadArgumentResolver.Java:115)
    at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.Java:77)
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.Java:127)
    at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.Java:100)
    at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.Java:113)
    ... 12 common frames omitted

例外はコンバーターがないことを示しています、そしてそれは本当です、私の問題はコンシューマー側でMappingJackson2MessageConverterコンバーターを設定する方法がわからないことです( org.springframework.messaging.converter.MappingJackson2MessageConverterを使用し、org.springframework.amqpは使用しないことに注意してください.support.converter.JsonMessageConverter

何かご意見は ?

念のため、次の場所でこのサンプルプロジェクトをフォークできます。 https://github.com/gustavoorsi/rabbitmq-consumer-receiver

10
Gustavo

はい、ようやくこれで動作しました。

Springは PayloadArgumentResolver を使用して、変換されたメッセージを抽出、変換し、 @ RabbitListener で注釈されたメソッドパラメータに設定します。どういうわけか、 mappingJackson2MessageConverter をこのオブジェクトに設定する必要があります。

したがって、CONSUMERアプリでは、 RabbitListenerConfigurer を実装する必要があります。 configureRabbitListeners(RabbitListenerEndpointRegistrar registrar)をオーバーライドすることで、カスタム DefaultMessageHandlerMethodFactory をこのファクトリーに設定でき、メッセージコンバーターを設定し、ファクトリーが作成します PayloadArgumentResolver と正しい変換。

これがコードのスニペットです。私は git project も更新しました。

ConsumerApplication.Java

package demo.consumer;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.stereotype.Service;

@SpringBootApplication
@EnableRabbit
public class ConsumerApplication implements RabbitListenerConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean
    public MappingJackson2MessageConverter jackson2Converter() {
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        return converter;
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setMessageConverter(jackson2Converter());
        return factory;
    }

    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
    }

    @Autowired
    private Receiver receiver;

}

@Service
class Receiver {
    @RabbitListener(queues = "queue")
    public void receiveMessage(Foo foo) {
        System.out.println("Received <" + foo.name + ">");
    }

    @RabbitListener(queues = "queue")
    public void receiveMessage(Bar bar) {
        System.out.println("Received <" + bar.age + ">");
    }
}

class Foo {
    public String name;
}

class Bar {
    public int age;
}

したがって、Producerマイクロサービスを実行すると、キューに2つのメッセージが追加されます。 1つはFooオブジェクトを表し、もう1つはBarオブジェクトを表します。コンシューマーマイクロサービスを実行すると、両方が Receiver クラスのそれぞれのメソッドによって消費されることがわかります。


更新された問題:

自分の側からのキューイングについては、概念的な問題があると思います。同じキューを指す @ RabbitListener で注釈が付けられた2つのメソッドを宣言することで、私が達成したかったことは不可能です。上記の解決策は適切に機能していませんでした。 rabbitmqに送信すると、たとえば、6つのFooメッセージと3つのBarメッセージがある場合、それらはFooパラメータを使用してリスナーによって6回受信されません。リスナーは並行して呼び出されるようですので、メソッドの引数の型に基づいて呼び出すリスナーを区別する方法はありません。私の解決策(これが最善の方法かどうかはわかりませんが、ここで提案を受け入れます)は、エンティティごとにキューを作成することです。だから今、私はqueue.barqueue.fooを持っており、 @ RabbitListener(queues = "queue.foo")もう一度、コードを更新しました。私の git repository で確認できます。

20
Gustavo

これを自分で行ったことはありませんが、RabbitTemplateを設定して適切な変換を登録する必要があるようです。 このSpringのドキュメント のセクション3.1.8をご覧ください。私はそれがAMQPクラスを使用して構成されていることを知っていますが、あなたが言及しているメッセージングクラスが互換性がある場合、それを置き換えることができない理由はありません。次のように見えます このリファレンス は、XMLではなくJava構成を使用してそれを行う方法を説明しています。私は実際にはRabbitを使用していないため、個人的な経験はありませんが、あなたが見つけたものを聞きたいです。

1
Rob Baily