spring-boot-starter-amqp踩坑記

踩坑記錄

近日在用spring boot架構一個微服務框架,服務發現與治理、發佈REST接口各類輕鬆愜意。可是服務當設計MQ入口時,就發現遇到無數地雷,如今整理成下文,供各路大俠圍觀與嘲笑。html

版本

當前使用的spring-boot-starter-amqp版本爲2016.5發佈的1.3.5.RELEASEgit

也許若干年後,大家版本都不會有這些問題了。:(github

RabbitMQ

當須要用到MQ的時候,個人第一反映就是使用RabbitMQ,貓了一眼spring boot的官方說明,上面說spring boot爲rabbit準備了spring-boot-starter-amqp,而且爲RabbitTemplate和RabbitMQ提供了自動配置選項。暗自竊喜~~spring

瞅瞅[官方文檔]http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-rabbitmq和例子,SO EASY,再看一眼GITHUB上的官方例了,也有例子。架構

心情愉悅的照着例子,開幹~~。app

踩坑

十五分鐘後的代碼相似這樣:框架

@Service
@RabbitListener(queues = "merchant")
public class MQReceiver  {
    protected Logger logger = Logger.getLogger(MQReceiver.class
            .getName()); 
  
    @RabbitHandler
    public void process(@Payload UpdateMerchant request) {
        UpdateMerchantResponse response = new UpdateMerchantResponse();
        logger.info(request.getMerchantId() + "->" + response.getReturnCode());
    }
}

消費信息後,應該記錄一條日誌。
結果獲得只有org.springframework.amqp.AmqpException: No method found for class [B 這個異常,而且還無限循環拋出這個異常。。。spring-boot

記得剛纔官方文檔好像說了異常什麼的,轉身去貓一眼,果真有:微服務

If retries are not enabled and the listener throws an exception, by default the delivery will be retried indefinitely. You can modify this behavior in two ways; set the defaultRequeueRejected
 property to false
 and zero re-deliveries will be attempted; or, throw an AmqpRejectAndDontRequeueException
 to signal the message should be rejected. This is the mechanism used when retries are enabled and the maximum delivery attempts are reached.this

知道了爲啥會無限重試了,下面來看看爲啥會拋出這個異常,google搜一下,貌似還有一個倒黴鬼遇到了這個問題

進去看完問題和大神的解答,豁然開朗。

There are two conversions in the @RabbitListener pipeline.
The first converts from a Spring AMQP Message to a spring-messaging Message.
There is currently no way to change the first converter from SimpleMessageConverter which handles String, Serializable and passes everything else as byte[].
The second converter converts the message payload to the method parameter type (if necessary).
With method-level @RabbitListeners there is a tight binding between the handler and the method.
With class-level @RabbitListener s, the message payload from the first conversion is used to select which method to invoke. Only then, is the argument conversion attempted.
This mechanism works fine with Java Serializable objects since the payload has already been converted before the method is selected.
However, with JSON, the first conversion returns a byte[] and hence we find no matching @RabbitHandler.
We need a mechanism such that the first converter is settable so that the payload is converted early enough in the pipeline to select the appropriate handler method.
A ContentTypeDelegatingMessageConverter is probably most appropriate.
And, as stated in AMQP-574, we need to clearly document the conversion needs for a @RabbitListener, especially when using JSON or a custom conversion.

得嘞,官方示例果真是坑,試試大神的解決方案,手動新增下轉換。

@Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

而後在生產和消費信息的地方使用他們:

@RabbitListener(queues = "merchant", containerFactory="rabbitListenerContainerFactory")
public void process(@Payload UpdateMerchant request) { 
     UpdateMerchantResponse response = new UpdateMerchantResponse();
    logger.info(request.getMerchantId() + "->" + response.getReturnCode());
 }

再來一次,果真能夠了

c.l.s.m.service.MQReceiver : 00000001->null

總結

看起來很簡單,但是掉坑裏面以後怎麼也得折騰個幾個小時才能爬出來,此文獻給掉進同一個坑的童鞋,但願你能滿意。

相關文章
相關標籤/搜索