消費者JmsListener應用源碼淺析

在之前的章節中,特別是《消費者本地事務》我們並沒有進行任何配置,爲什麼事務生效了呢(不考慮關係型數據庫的結合,結合後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 extends MessageListenerAdapter{@JmsListener(destination="queue.orders",concurrency="5-10")public void placeOrder(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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章