весенняя загрузка rabbitmq MappingJackson2MessageConverter преобразование пользовательских объектов

Я пытаюсь создать простое приложение spring boot с spring boot, которое "производит" сообщения в RabbitMQ exchange / queue и другое приложение spring boot, которое "потребляет" эти сообщения. Поэтому у меня есть два приложения (или микросервисы, если хотите). 1) "производитель" конструирование 2) "потребитель" конструирование

"производитель" имеет 2 объекта домена. Foo и Bar, который должен быть преобразован в json и отправлен в rabbitmq. "Потребитель" должен получить и преобразовать сообщение json в домен Foo и бар соответственно. По какой-то причине я не могу выполнить эту простую задачу. Примеров этому немного. Для конвертера сообщений я хочу использовать org.springframework.обмен сообщениями.конвертер.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.access1(SimpleMessageListenerContainer.java:83)
    at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.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.access0(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.обмен сообщениями.конвертер.MappingJackson2MessageConverter, а не org.springframework.протокол AMQP.поддержка.конвертер.JsonMessageConverter)

какие мысли ?

на всякий случай, вы можете скопировать этот образец проекта по: https://github.com/gustavoorsi/rabbitmq-consumer-receiver

2 ответов


хорошо, я наконец-то получил эту работу.

Spring использует PayloadArgumentResolver для извлечения, преобразования и установки преобразованного сообщения в параметр метода с аннотацией @RabbitListener. Как - то нам нужно установить mappingJackson2MessageConverter в этот объект.

Итак, в потребительском приложении нам нужно реализовать RabbitListenerConfigurer. Путем переопределения configureRabbitListeners(RabbitListenerEndpointRegistrar регистратор) мы можем установить пользовательский DefaultMessageHandlerMethodFactory, к этой фабрике мы установили конвертер сообщения, и фабрика создаст наше PayloadArgumentResolver С правильным преобразованием.

вот фрагмент кода, я также обновил проект git.

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;
}

Так, если вы выполните конструирование производитель добавляет 2 сообщений в очереди. Один из них представляет объект Foo, а другой - объект Bar. Запустив микросервис потребителя, вы увидите, что оба они потребляются соответствующим методом в приемник класса.


обновленный вопрос:

есть концептуальная проблема, по очереди с моей стороны я думаю. Что я хотел достичь невозможно, объявив 2 методы аннотировано с @RabbitListener это указывает на ту же очередь. Вышеприведенное решение не работало должным образом. Если вы отправляете в rabbitmq, скажем, 6 сообщений Foo и 3 сообщения Bar, они не будут получены 6 раз слушателем с параметром Foo. Кажется, что слушатель вызывается параллельно, поэтому нет способа различать, какой слушатель вызывать на основе типа аргумента метода. Мое решение (и я не уверен, что это лучший способ, я открыт для предложений здесь) заключается в том, чтобы создайте очередь для каждой сущности. Так что теперь у меня есть очереди.бар и очереди.фу!--42-->, и обновление @RabbitListener (queues = " очередь.foo") Еще раз, я обновил код, и вы можете проверить его в моем репозиторий git.


сам этого не делал, но, похоже, вам нужно зарегистрировать соответствующие преобразования, настроив RabbitTemplate. Взгляните на раздел 3.6.2 в эта весенняя документация. Я знаю, что он настроен с использованием классов AMQP, но если класс обмена сообщениями, который вы упоминаете, совместим, нет причин, по которым вы не можете его заменить. Похоже на этой ссылке объясняет, как это можно сделать с помощью конфигурации Java, а не XML. Я действительно не используется Кролик, поэтому у меня нет личного опыта, но я хотел бы услышать, что вы узнали.