近日在用spring boot架构一个微服务框架,服务发现与治理、发布REST接口各类轻松惬意。可是服务当设计MQ入口时,就发现遇到无数地雷,如今整理成下文,供各路大侠围观与嘲笑。html
当前使用的spring-boot-starter-amqp版本为2016.5发布的1.3.5.RELEASEgit
也许若干年后,大家版本都不会有这些问题了。:(github
当须要用到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
看起来很简单,但是掉坑里面以后怎么也得折腾个几个小时才能爬出来,此文献给掉进同一个坑的童鞋,但愿你能满意。