消费者JmsListener应用源码浅析

来源:互联网 发布:故宫淘宝营销策略分析 编辑:程序博客网 时间:2024/06/06 05:02
在之前的章节中,特别是《消费者本地事务》我们并没有进行任何配置,为什么事务生效了呢(不考虑关系型数据库的结合,结合后MQ事务仍然生效,但关系型数据库事务没有作为整体事务管理),下面我们通过对源码的分析来探索Spring如何帮助我们实现了。

本章概要

1、JmsListener注解解析
2、其他消费方式实现(3种)
3、分布式事务的支持

JmsListener注解解析

1、通过JmsListener注解监听消费,实现了异步消费;

2、在官方文档中已经说明需要支持@JmsListener注解,则需要在任意@Configuration类添加@EnableJms注解,同时还需要配置DefaultJmsListenerContainerFactory的Bean实例,如下

在springboot中通过JmsAnnotationDrivenConfiguration对相关bean实例实现注册。在注册的DefaultJmsListenerContainerFactoryConfigurer中可以发现其支持JTA分布式事务管理,以及DefaultJmsListenerContainerFactoryConfigurer中的configure方法能够看到如下代码:
if (this.transactionManager != null) {
factory.setTransactionManager(this.transactionManager);
} else {
factory.setSessionTransacted(Boolean.valueOf(true));
}
在没有配置外部事务管理器时默认支持jms内部的session事务机制,这也就很好的解释了为什么在@JmsListener注解下的消费者本地事务在没有进行任何配置的情况下已经生效,甚至能够很好的将消息的消费与生产在一个事务中有效处理。
继续往下看源码,在DefaultJmsListenerContainerFactory中定义了创建了DefaultMessageListenerContainer消息监听容器,后续很多监听参数的设置均对其设置即可(如durableSubscriptionName支持持久化订阅);

而DefaultJmsListenerContainerFactory继承于AbstractJmsListenerContainerFactory,其中定义了真正创建DefaultMessageListenerContainer对象的方法
public C createListenerContainer(JmsListenerEndpoint endpoint) {
AbstractMessageListenerContainer instance =createContainerInstance();
........
endpoint.setupListenerContainer(instance);
initializeContainer(instance);
return instance;
}
protected abstract C createContainerInstance();
protected void initializeContainer(C instance) {
}
问题来了,spring是何时调用了createListenerContainer方法创建了DefaultMessageListenerContainer呢,在JmsListenerEndpointRegistrar有如下几个方法定义

其中resolveContainerFactory获取在JmsAnnotationDrivenConfiguration注册的DefaultJmsListenerContainerFactory,提供register**方法注册对应的端点,
继续跟踪this.endpointRegistry.registerListenerContainer(descriptor.endpoint,resolveContainerFactory(descriptor));方法,其实现在JmsListenerEndpointRegistry中,
红色标示部分可以看到我们正是通过之前获取的DefaultJmsListenerContainerFactory调用createListenerContainer方法定义了最终的消息监听容器DefaultMessageListenerContainer。到这里其实还没有结束,继续往下找最终的消息监听事件处理在MessageListenerAdapter中的onMessage-->invokeListenerMethod方法执行处理。

以上是java配置方式,我们也可以通过XML配置,来源官网截图:


其他消费方式

方式一,从官方可以看到下面的描述,如果我们不使用@JmsListener注解进行消息的消费监听,可以在编码中通过JmsListenerEndpoint配置JmsListenerConfigurer替代@JmsListener

spring提供了SimpleJmsListenerEndpoint协助我们进行配置,JmsListenerEndpointRegistrar在上述@JmsListener也有所涉及,最重要的还是endpoint.setMessageListener(message -> { // processing});
最终消息的监听处理在我们定义的MessageListener中实现。
我们需要定义消息监听MessageListener的实现类,其有3中实现方式:MessageListener、SessionAwareMessageListener和MessageListenerAdapter。
1、MessageListener:实现异步监听,通过onMessage方法处理唯一参数Message。MessageListener的设计只是纯粹用来接收消息的,假如我们在使用MessageListener处理接收到的消息时我们需要发送一个消息通知对方我们已经收到这个消息了,那么这个时候我们就需要在代码里面去重新获取一个Connection或Session;
2、SessionAwareMessageListener:SessionAwareMessageListener是Spring为我们提供的,它不是标准的JMS MessageListener。SessionAwareMessageListener的设计就是为了方便我们在接收到消息后发送一个回复的消息,它同样为我们提供了一个处理接收到的消息的onMessage方法,但是这个方法可以同时接收两个参数,一个是表示当前接收到的消息Message,另一个就是可以用来发送消息的Session对象。
3、MessageListenerAdapter:MessageListenerAdapter类实现了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是将接收到的消息进行类型转换,然后通过反射的形式把它交给一个普通的Java类进行处理。其另外一个主要的功能是可以自动的发送返回消息。当我们用于处理接收到的消息的方法的返回值不为空的时候,Spring会自动将它封装为一个JMS Message,然后自动进行回复。回复后的消费者有两种定义方式:
3.1、通过发送的Message的setJMSReplyTo方法指定该消息对应的回复消息的目的地。
3.2、通过MessageListenerAdapter的defaultResponseDestination属性来指定。

方式二:通过XML方式配置,官方还支持JMS命名空间配置,并且支持@JmsListener注解监听的使用

官方配置案例

其中jms:listener中ref即为我们需要定义实现的监听类,结合方式一种的监听介绍,我们更多的采用继承MessageListenerAdapter类实现。
如下:
@Component("orderService")public class OrderService extendsMessageListenerAdapter{@JmsListener(destination="queue.orders",concurrency="5-10")public voidplaceOrder(Message message, Session session) throws JMSException {try {Object object= getMessageConverter().fromMessage(message);System.out.println(object.toString());System.out.println(session);message.acknowledge();//此时我们可以手动签收} catch (MessageConversionException | JMSException e) {e.printStackTrace();}}}


方式三:根据上述@JmsListener的源码分析,其实我们已经可以注意到,无论是DefaultJmsListenerContainerFactoryConfigurer、DefaultJmsListenerContainerFactory都是在帮助我们一步步靠近DefaultJmsListenerContainer,其实我们还能够直接注册DefaultJmsListenerContainer的实例实现消息的监听处理,可以通过java或者XML配置实现,基本如下格式:
<!-- 配置JMS连接工厂 -->  
    <bean id="myConnectionFactory"  
        class="org.springframework.jms.connection.CachingConnectionFactory">  
        <!-- Session缓存数量 -->  
        <property name="sessionCacheSize" value="10" />  
        <!-- 接收者ID -->  
        <property name="clientId" value="client_01" />  
        <property name="targetConnectionFactory">  
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">  
                <!-- MQ地址 -->  
                <property name="brokerURL" value="tcp://localhost:61626" />  
            </bean>  
        </property>  
    </bean>  
<!-- 监听消息的目的地(一个主题) -->  
<bean id="myDestination" class="org.apache.activemq.command.ActiveMQTopic">  
        <!-- 消息主题的名字 -->  
        <constructor-arg index="0" value="my.topic" />  
    </bean>  
  
    <!-- 消费消息配置 (自己定义)-->  
    <bean id="myTopicConsumer" class="com.shf.jms.JMSReceiver" />  
  
    <!-- 消息监听器 -->  
    <bean id="myTopicListener"  
        class="org.springframework.jms.listener.adapter.MessageListenerAdapter">  
        <constructor-arg ref="myTopicConsumer" />  
        <!-- 接收消息的方法名称 -->  
        <property name="defaultListenerMethod" value="receive" />  
        <!-- 不进行消息转换 -->  
        <property name="messageConverter"><null/></property>  
    </bean>  
  
    <!-- 消息监听容器 -->  
    <bean id="myListenerContainer"  
        class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
        <property name="connectionFactory" ref="myConnectionFactory" />  
        <!-- 发布订阅模式 -->  
        <property name="pubSubDomain" value="true"/>  
        <!-- 消息持久化 -->  
        <property name="subscriptionDurable" value="true"/>  
        <property name="receiveTimeout" value="10000"/>  
        <!-- 接收者ID -->  
        <property name="clientId" value="client_01" />  
        <property name="durableSubscriptionName" value="client_01"/>  
        <property name="destination" ref="myDestination" />  
        <property name="messageListener" ref="myTopicListener" />  
    </bean>  
将XML配置转为Java配置并不复杂,这里就不实现了。


分布式事务的支持

消息监听容器(MessageListenerContainer)用来从jms 消息队列中接受消息,然后推送注册到它内部的消息监听器(MessageListener)中。spring提供了两种标准的jms 消息监听容器,特色如下:
SimpleMessageListenerContainer:在启动时,创建固定数目的jms 会话和一个消费者,使用标准的jmsMessageConsumer.setMessageListener()方法来注册监听器,让jms 提供者来让监听器返回。
DefaultMessageListenerContainer:支持在运行时动态适应,并且能参与到外部受管理事务。每个接收到的消息使用JtaTransactionManager注册为XA 事务,因而可以充分利用xa 事务语义进行处理。
由于在实际项目中很多场景需要XA事务的实现,故我们通常采用DefaultMessageListenerContainer配置消息监听容器。

同时还要注意,在《优化生产者连接工厂(带有session缓存)》章节中我们验证的无论是否采用XA-ConnectionFactory,事务均能够生效,那么在消费者中,配合JtaTransactionManager需要同步采用ActiveMQXAConnectionFactory。

0 0