参考:Spring整合rabbitmq实践(三):源码-@RabbitListener实现过程
rabbitmq consumer push模式还是pull模式 参考朱忠华老师 RabbitMQ之Consumer消费模式(Push & Pull)
为什么一个普通的方法加上@RabbitListener注解就能接收消息了呢?
先总结来说,有一个BeanPostProcessor来处理这个注解,把注解相关的内容取出来,封装成一个RabbitListenerEndPoint。然后给每个Endpoint创建一个MessageListenerContainer,在这个container中注册一个MessageListener,在这个MessageListener中创建了一个HandlerAdapter,这个adapter与rabbitmq broker建立一个connection,接收rabbitmq broker push过来的message,放到一个blocking queue中。至此完成消息的接收。
接下来是消息的处理。上文的adapter把我们用@RabbitListener注解的普通方法通过反射的方式还原出来,从blocking queue中poll出一个一个的message,进行处理。
从源码一步一步来看其是如何实现的
从@RabbitListener最上面的注释,可以看到
@RabbitListener的处理器是RabbitListenerAnnotationBeanPostProcessor
RabbitListenerAnnotationBeanPostProcessor
其implements BeanPostProcessor,实现了postProcessAfterInitialization方法,当bean初始化完成后,执行这个方法
去执行buildMetadata方法
在这个方法里面,找出了所有加了@RabbitListener注解的方法。
之后执行processAmqpListener
其实通过这个方法可以看到@RabbitListener的另一种使用方式,可以在类上加@RabbitListener注解,然后在方法上加@RabbitHandler注解,如果采用这种方式会processMultiMethodListeners()方法来处理这些方法。
之后调用processListener方法,方法较长
读取@RabbitListener注解中的值,设置到endpoint中去。
直接看最后一段,endpoint的属性都设置完了之后,获取我们配置的RabbitListenerContainerFactory bean,然后调用RabbitListenerEndpointRegistrar类的registerEndpoint()方法
RabbitListenerContainerFactory可以是默认的,也可以自定义。可以是这样在代码里面以@Bean来指定的
然后使用时指定containerFactory
RabbitListenerEndpointRegistrar
调用RabbitListenerEndpointRegistrar类的registerEndpoint()方法
这里根据startImmediately看是否需要立刻注册endpoint,或者先将其添加到一个List,稍后统一注册。
对于统一注册的实现,RabbitListenerAnnotationBeanPostProcessor类除了实现BeanPostProcessor以外,还实现了SmartInitializingSingleton接口,所以当RabbitListenerAnnotationBeanPostProcessor这个bean实例化完成之后会调用它的afterSingletonsInstantiated()方法
因为之前已经将所有的endpoint添加到了RabbitListenerEndpointRegistrar类中的一个List中了,所以这里调用RabbitListenerEndpointRegistrar类的afterPropertiesSet()方法进行统一注册:
这里就是一个简单的for循环,一个一个注册,具体是怎么注册的,再跟踪registerListenerContainer()方法
RabbitListenerEndpointRegistry
通过注释可以考到,其create a message listener container for the given RabbitListenerEndpoint
关键代码
MessageListenerContainer container = createListenerContainer(endpoint, factory);
containerGroup = new ArrayList<MessageListenerContainer>();
可见,注册endpoint,实际上就是RabbitListenerContainerFactory将每一个endpoint都创建成MessageListenerContainer(具体创建过程,由RabbitListenerContainerFactory类自己去完成),然后根据startImmediately参数判断是否调用startIfNecessary()方法立即启动MessageListenerContainer。
实际接收消息是由这个MessageListenerContainer来做的,而MessageListenerContainer接口中有一个接口方法来设置MessageListener,
AbstractMessageListenerContainer
通过注释,可以看到 MessageListener Object或ChannelAwareMessageListener
MessageListener
listener interface to receive asynchronous delivery of Amqp Message
或
这样接收并处理消息的所有工作就完成了。
如果不立即启动MessageListenerContainer,RabbitListenerEndpointRegistry也实现了SmartLifecycle接口,所以在spring context refresh的最后一步会去调用start()方法:
可以看到在这里统一启动了所有的MessageListenerContainer
所谓启动MessageListenerContainer其实就是调用MessageListenerContainer的start()方法。这也是SmartLifecycle的一个接口方法,它的实现必须保证调用了这个start()方法之后MessageListenerContainer将能够接受到消息。
所以对@RabbitListener注解的整个处理流程就是这样。
总结一下整个实现流程:
@RabbitListener注解的方法所在的类首先是一个bean,因此,实现BeanPostProcessor接口对每一个初始化完成的bean进行处理。
遍历bean中由用户自己定义的所有的方法,找出其中添加了@RabbitListener注解的方法(也可以是@RabbitHandler注解,上面已经讲了,不再赘述)。
读取上面找出的所有方法上@RabbitListener注解中的值,并为每一个方法创建一个RabbitListenerEndpoint,保存在RabbitListenerEndpointRegistrar类中。
在所有的bean都初始化完成,即所有@RabbitListener注解的方法都创建了endpoint之后,由我们配置的RabbitListenerContainerFactory将每个endpoint创建MessageListenerContainer。
最后启动上面创建的MessageListenerContainer。
至此,全部完成,MessageListenerContainer启动后将能够接受到消息,再将消息交给它的MessageListener处理消息。
下面还剩下几件事情才能真正实现上面的步骤:
1. RabbitListenerContainerFactory只是个接口,它不会自己创建MessageListenerContainer,所以需要一个RabbitListenerContainerFactory实现类,它必须能创建MessageListenerContainer。
2. MessageListenerContainer也只是一个接口,它不会自己接收消息,所以需要一个MessageListenerContainer实现类,它必须做到在启动后能够接收消息,同时它必须能设置MessageListener,用以处理消息。
3. MessageListener(或ChannelAwareMessageListener)也只是一个接口,所以还需要一个MessageListener实现类,它必须能调用我们加了@RabbitListener注解的方法,这样才实现了消息的处理
SimpleRabbitListenerContainerFactory
通过SimpleRabbitListenerContainerFactory创建MesageListenerContainer
MessageListenerContainer的创建
之前的关键代码 RabbitListenerEndpointRegistry的registerListenerContainer方法
MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);
createListenerContainer跟踪进去
会进入到AbstractRabbitListenerContainerFactory的createListenerContainer方法中,其关键代码
注意里面两个方法,
initializeContainer这个方法里面,进入到SimpleRabbitListenerContainerFactory类中,会做一些它独有的属性设置
setupListenerContainer方法执行结束,MessageListener就设置到MessageListenerContainer里面去了,可以跟踪这个方法
一直到AbstractRabbitListenerEndpoint类的下面这个方法:
可以看到在这个方法里创建了MessageListener,并将其设置到MessageListenerContainer里面去。
createMessageListener()方法有两个实现,实际调用的是MethodRabbitListenerEndpoint类里面的实现:
关键代码
MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
messageListener.setHandlerMethod(configureListenerAdapter(messageListener));
看到setHandlerMethod(configureListenerAdapter(messageListener))这一行,这里创建并设置了一个HandlerAdapter,这个HandlerAdapter能够调用我们加了@RabbitListener注解的方法。
SimpleMessageListenerContainer接收消息的实现
SimpleRabbitListenerContainerFactory创建的MessageListenerContainer是SimpleMessageListenerContainer类,下面看它是怎么在启动后就能接收消息的。
上面讲过RabbitListenerEndpointRegistry类通过调用MessageListenerContainer的start()方法类启动这个MessageListenerContainer
SimpleMessageListenerContainer类本身并没有实现start()方法,在它继承的抽象父类里面。进入AbstractMessageListenerContainer抽象类找到start()方法的实现
MessageListenerContainer extends SmartLifecycle
SmartLifecycle extends Lifecycle
而AbstractMessageListenerContainer implements MessageListenerContainer
真正的启动方法是doStart(),所以去SimpleMessageListenerContainer类中找这个类的doStart()实现:
doStart()这里就涉及到多态了,子类执行子类override的方法
Re-initializes this container's Rabbit message consumers, if not initialized already. Then submits each consumer to this container's task executor.
关键代码
int newConsumers = initializeConsumers();
这个方法创建了BlockingQueueConsumer,数量等于concurrentConsumers参数的配置。
for (BlockingQueueConsumer consumer : this.consumers) {
AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
processors.add(processor);
getTaskExecutor().execute(processor);
if (getApplicationEventPublisher() != null) {
getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
}
}
另一个方法是getTaskExecutor().execute(processor),前面用BlockingQueueConsumer创建了AsyncMessageProcessingConsumer(实现了Runnable接口),这里获取到Executor来执行,每一个MessageListenerContainer都有各自的Executor。
在AsyncMessageProcessingConsumer类的run()方法里面可以找到下面这段代码
这里有两个地方需要注意。
一个是this.consumer.hasDelivery()
private final BlockingQueue<Delivery> queue;
另一个要注意的是receiveAndExecute()方法
boolean receivedOk = receiveAndExecute(this.consumer); // At least one message received
关键代码
Message message = consumer.nextMessage(this.receiveTimeout);
所以SimpleMessageListenerContainer接收消息的实现方案是:用一个BlockingQueue保存rabbitmq发过来还未来得及处理的消息,然后向Executor提交执行Runnable,Runnable中循环从BlockingQueue里面取消息。
这个BlockingQueue里面的消息是怎么从rabbitmq获取到的呢?下面看一下
BlockingQueueConsumer获取Message
这个地方应该是从BlockingQueueConsumer来看
先找
this.queue.put,找到方法handleDelivery
其override的这个方法是InternalConsumer in BlockingQueueConsumer
有一个InternalConsumer
private InternalConsumer consumer;
看其start方法
关键代码
his.channel.basicQos(this.prefetchCount)
可以通过 “channel.basicQos(10)” 这个方法来设置当前channel的prefetch count。
举个例子,比如你要是设置为10的话,那么意味着当前这个channel里,unack message的数量不能超过10个,以此来避免消费者服务实例积压unack message过多。
默认的prefetchCount为250
另一个关键代码
consumeFromQueue(queueName);
关键代码
this.channel.basicConsume
所以spring-amqp是broker push模式
继续看,把读到的消息放到consumer tag里面
在handleDelivery中
BlockingQueueConsumer.this.queue.put(new Delivery(consumerTag, envelope, properties, body));
就把consumerTag中的内容放到queue里面,这样当执行nextMessage的时候就能获取到了
那么handleDelivery什么时候调用呢?
看Consumer接口是如何写的
可以看到当一个basic.deliver被consumer接收到了,就调用
这里涉及到rabbitmq broker与rabbitmq consumer之间的连接等一些内容,补充一下
如果你写好了一个消费者服务的代码,让他开始从RabbitMQ消费数据,这时这个消费者服务实例就会自己注册到RabbitMQ。
所以,RabbitMQ其实是知道有哪些消费者服务实例存在的。
大家看看下面的图,直观的感受一下:
接着,RabbitMQ就会通过自己内部的一个“basic.delivery”方法来投递消息到仓储服务里去,让他消费消息。
而rabbitmq broker内部的basic.delivery其实是被consumer感知的,会调用handleDelivery
那么可能就是前面consumerFromQueue的时候,执行this.channel.basicConsumer的时候,调用ConsumerDecorator的delivery方法,传入this.consumer,就是InternalConsumer,并赋值给delegate,然后delegate再调用handleDelivery,放到this.queue里面
SimpleMessageListenerContainer处理消息的实现
上面的receiveAndExecute()方法接收消息的同时也将其处理了,关键代码
executeListener(channel, message);
在这个方法里面可以看到invokeListener()方法,
这里有个proxy,这个proxy是由下面这个方法创建的匿名类
这个方法里可以看到doInvokeListener()方法,已经差不多接近我们的@RabbitListener注解的方法了
可以看到其最后进来了
进入MessagingMessageListenerAdapter类的onMessage()方法
这里通过invokHandler()方法消费获取到的message,然后在catch里面处理异常,进入invokeHandler()方法
在这里可以看到catch了所有的异常,也就是说只要是我们消费消息的方法里面抛出来的异常全都会被包装成ListenerExecutionFailedException,并且这个Exception里面把消息也放进去了。
这里的this.handlerMethod其实就是上面提到的HandlerAdapter
private HandlerAdapter handlerMethod;
跟踪它的invoke()方法,看它是怎么调用我们@RabbitListener注解的方法的。
这里通过getBridgedMethod()方法拿到的就是@RabbitListener注解的方法了,这是在刚开始处理@RabbitListener注解时就已经保存下来的,然后就可以利用反射来调用这个方法,这样就完成了接收并处理消息的整个流程。
RabbitTemplate也是使用broker push的方式
另外,如果使用RabbitmqTemplate来接收message,也是broker push的方式
从RabbitTemplate中只有queueName入参的方法开始:
receiveTimeOut参数为0,直接获取消息,不等待,获取不到返回null;否则会等待一段时间。
看到Message是通过调用execute方法得到的,进到execute方法:
这里能看到配置RetryTemplate的作用,具体就不管了,找到doExecute方法,Message是从这里得到的:
这个方法比较长,大体可以了解到,在这个方法里创建了Connection和Channel,执行action.doInRabbit()方法得到Message,关闭Channel和Connection。
当然,这里Connection和Channel的创建和关闭都不一定是真的创建和关闭,与具体的实现有关,比如CachingConnectionFactory,它的实现就是有缓存的,后面详述。
action.doInRabbit()方法的实现逻辑就要再回到上面的receive方法,这里的action就是在那个receive方法传入的一个ChannelCallback的匿名内部实现类。
action为这个匿名内部类
channel -> {
Delivery delivery = consumeDelivery(channel, queueName, timeoutMillis);
if (delivery == null) {
return null;
}
else {
if (isChannelLocallyTransacted(channel)) {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
channel.txCommit();
}
else if (isChannelTransacted()) {
ConnectionFactoryUtils.registerDeliveryTag(getConnectionFactory(), channel,
delivery.getEnvelope().getDeliveryTag());
}
else {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
return buildMessageFromDelivery(delivery);
}
}
可以看到最后返回的消息是从Delivery中得到的,那么看下Delivery是怎么来的
看到future.get(),显然这是一个阻塞式的等待返回结果,receive方法中传入的receiveTimeout参数也正是在这里用到的。那么future数据自然是在createConsumer()方法中产生的
关键代码
DefaultConsumer consumer = createConsumer(queueName, channel, future,
timeoutMillis < 0 ? DEFAULT_CONSUME_TIMEOUT : timeoutMillis);
可以看到
关键代码
channel.basicQos(1);
channel.basicConsume(queueName, consumer);
于是可以看到RabbitTemplata使用broker push方式
channel.basicConsume(queueName, consumer)是rabbitmq的api。
整体上的方式与@RabbitListener在BlockingQueueConsumer获取Message方式是一样的