Spring整合ActiveMQ 實現消息服務

Spring整合JMS——基於ActiveMQ實現(一)

1.1     JMS簡介

       JMS的全稱是Java Message Service,即Java消息服務。它主要用於在生產者和消費者之間進行消息傳遞,生產者負責產生消息,而消費者負責接收消息。把它應用到實際的業務需求中的話我們可以在特定的時候利用生產者生成一消息,並進行發送,對應的消費者在接收到對應的消息後去完成對應的業務邏輯。對於消息的傳遞有兩種類型,一種是點對點的,即一個生產者和一個消費者一一對應;另一種是發佈/訂閱模式,即一個生產者產生消息並進行發送後,可以由多個消費者進行接收。

1.2     Spring整合JMS

       對JMS做了一個簡要介紹之後,接下來就講一下Spring整合JMS的具體過程。JMS只是一個標準,真正在使用它的時候我們需要有它的具體實現,這裏我們就使用Apache的activeMQ來作爲它的實現。所使用的依賴利用Maven來進行管理,具體依賴如下:

 

Xml代碼 
  1. <dependencies>  
  2.         <dependency>  
  3.             <groupId>junit</groupId>  
  4.             <artifactId>junit</artifactId>  
  5.             <version>4.10</version>  
  6.             <scope>test</scope>  
  7.         </dependency>  
  8.         <dependency>  
  9.             <groupId>org.springframework</groupId>  
  10.             <artifactId>spring-context</artifactId>  
  11.             <version>${spring-version}</version>  
  12.         </dependency>  
  13.         <dependency>  
  14.             <groupId>org.springframework</groupId>  
  15.             <artifactId>spring-jms</artifactId>  
  16.             <version>${spring-version}</version>  
  17.         </dependency>  
  18.         <dependency>  
  19.             <groupId>org.springframework</groupId>  
  20.             <artifactId>spring-test</artifactId>  
  21.             <version>${spring-version}</version>  
  22.         </dependency>  
  23.         <dependency>  
  24.             <groupId>javax.annotation</groupId>  
  25.             <artifactId>jsr250-api</artifactId>  
  26.             <version>1.0</version>  
  27.         </dependency>  
  28.         <dependency>  
  29.             <groupId>org.apache.activemq</groupId>  
  30.             <artifactId>activemq-core</artifactId>  
  31.             <version>5.7.0</version>  
  32.         </dependency>  
  33. </dependencies> 

1.2.1 activeMQ準備

       既然是使用的apache的activeMQ作爲JMS的實現,那麼首先我們應該到apache官網上下載activeMQ(http://activemq.apache.org/download.html),進行解壓後運行其bin目錄下面的activemq.bat文件啓動activeMQ。

1.2.2配置ConnectionFactory

       ConnectionFactory是用於產生到JMS服務器的鏈接的,Spring爲我們提供了多個ConnectionFactory,有SingleConnectionFactory和CachingConnectionFactory。SingleConnectionFactory對於建立JMS服務器鏈接的請求會一直返回同一個鏈接,並且會忽略Connection的close方法調用。CachingConnectionFactory繼承了SingleConnectionFactory,所以它擁有SingleConnectionFactory的所有功能,同時它還新增了緩存功能,它可以緩存Session、MessageProducer和MessageConsumer。這裏我們使用SingleConnectionFactory來作爲示例。

Xml代碼 複製代碼 
  1. <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"/> 

       這樣就定義好產生JMS服務器鏈接的ConnectionFactory了嗎?答案是非也。Spring提供的ConnectionFactory只是Spring用於管理ConnectionFactory的,真正產生到JMS服務器鏈接的ConnectionFactory還得是由JMS服務廠商提供,並且需要把它注入到Spring提供的ConnectionFactory中。我們這裏使用的是ActiveMQ實現的JMS,所以在我們這裏真正的可以產生Connection的就應該是由ActiveMQ提供的ConnectionFactory。所以定義一個ConnectionFactory的完整代碼應該如下所示:

Xml代碼 複製代碼 
  1. <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->  
  2. <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">  
  3.     <property name="brokerURL" value="tcp://localhost:61616"/>  
  4. </bean>  
  5.   
  6. <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->  
  7. <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">  
  8.     <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->  
  9.     <property name="targetConnectionFactory" ref="targetConnectionFactory"/>  
  10. </bean>  

1.2.3配置生產者

配置好ConnectionFactory之後我們就需要配置生產者。生產者負責產生消息併發送到JMS服務器,這通常對應的是我們的一個業務邏輯服務實現類。但是我們的服務實現類是怎麼進行消息的發送的呢?這通常是利用Spring爲我們提供的JmsTemplate類來實現的,所以配置生產者其實最核心的就是配置進行消息發送的JmsTemplate。對於消息發送者而言,它在發送消息的時候要知道自己該往哪裏發,爲此,我們在定義JmsTemplate的時候需要往裏面注入一個Spring提供的ConnectionFactory對象。

Xml代碼 複製代碼 
  1. <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->  
  2. <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">  
  3.     <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->  
  4.     <property name="connectionFactory" ref="connectionFactory"/>  
  5. </bean> 

       在真正利用JmsTemplate進行消息發送的時候,我們需要知道消息發送的目的地,即destination。在Jms中有一個用來表示目的地的Destination接口,它裏面沒有任何方法定義,只是用來做一個標識而已。當我們在使用JmsTemplate進行消息發送時沒有指定destination的時候將使用默認的Destination。默認Destination可以通過在定義jmsTemplate bean對象時通過屬性defaultDestination或defaultDestinationName來進行注入,defaultDestinationName對應的就是一個普通字符串。在ActiveMQ中實現了兩種類型的Destination,一個是點對點的ActiveMQQueue,另一個就是支持訂閱/發佈模式的ActiveMQTopic。在定義這兩種類型的Destination時我們都可以通過一個name屬性來進行構造,如:

 


Xml代碼 複製代碼 
  1. <!--這個是隊列目的地,點對點的-->  
  2. <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">  
  3.     <constructor-arg>  
  4.         <value>queue</value>  
  5.     </constructor-arg>  
  6. </bean>  
  7. <!--這個是主題目的地,一對多的-->  
  8. <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">  
  9.     <constructor-arg value="topic"/>  
  10. </bean>  

       假設我們定義了一個ProducerService,裏面有一個向Destination發送純文本消息的方法sendMessage,那麼我們的代碼就大概是這個樣子:


Java代碼 複製代碼 
  1. package com.tiantian.springintejms.service.impl;   
  2.     
  3. import javax.annotation.Resource;   
  4. import javax.jms.Destination;   
  5. import javax.jms.JMSException;   
  6. import javax.jms.Message;   
  7. import javax.jms.Session;   
  8.     
  9. import org.springframework.jms.core.JmsTemplate;   
  10. import org.springframework.jms.core.MessageCreator;   
  11. import org.springframework.stereotype.Component;   
  12.     
  13. import com.tiantian.springintejms.service.ProducerService;   
  14.     
  15. @Component  
  16. public class ProducerServiceImpl implements ProducerService {   
  17.     
  18.     private JmsTemplate jmsTemplate;   
  19.        
  20.     public void sendMessage(Destination destination, final String message) {   
  21.         System.out.println("---------------生產者發送消息-----------------");   
  22.         System.out.println("---------------生產者發了一個消息:" + message);   
  23.         jmsTemplate.send(destination, new MessageCreator() {   
  24.             public Message createMessage(Session session) throws JMSException {   
  25.                 return session.createTextMessage(message);   
  26.             }   
  27.         });   
  28.     }    
  29.   
  30.     public JmsTemplate getJmsTemplate() {   
  31.         returnjmsTemplate;   
  32.     }    
  33.   
  34.     @Resource  
  35.     public void setJmsTemplate(JmsTemplate jmsTemplate) {   
  36.         this.jmsTemplate = jmsTemplate;   
  37.     }   
  38.     
  39. }  
package com.tiantian.springintejms.service.impl; import javax.annotation.Resource;import javax.jms.Destination;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.Session; import org.springframework.jms.core.JmsTemplate;import org.springframework.jms.core.MessageCreator;import org.springframework.stereotype.Component; import com.tiantian.springintejms.service.ProducerService; @Componentpublic class ProducerServiceImpl implements ProducerService {     private JmsTemplate jmsTemplate;        public void sendMessage(Destination destination, final String message) {        System.out.println("---------------生產者發送消息-----------------");        System.out.println("---------------生產者發了一個消息:" + message);        jmsTemplate.send(destination, new MessageCreator() {            public Message createMessage(Session session) throws JMSException {                return session.createTextMessage(message);            }        });    }     public JmsTemplate getJmsTemplate() {        returnjmsTemplate;    }     @Resource    public void setJmsTemplate(JmsTemplate jmsTemplate) {        this.jmsTemplate = jmsTemplate;    } }

 

 

 

       我們可以看到在sendMessage方法體裏面我們是通過jmsTemplate來發送消息到對應的Destination的。到此,我們生成一個簡單的文本消息並把它發送到指定目的地Destination的生產者就配置好了。

1.2.4配置消費者

生產者往指定目的地Destination發送消息後,接下來就是消費者對指定目的地的消息進行消費了。那麼消費者是如何知道有生產者發送消息到指定目的地Destination了呢?這是通過Spring爲我們封裝的消息監聽容器MessageListenerContainer實現的,它負責接收信息,並把接收到的信息分發給真正的MessageListener進行處理。每個消費者對應每個目的地都需要有對應的MessageListenerContainer。對於消息監聽容器而言,除了要知道監聽哪個目的地之外,還需要知道到哪裏去監聽,也就是說它還需要知道去監聽哪個JMS服務器,這是通過在配置MessageConnectionFactory的時候往裏面注入一個ConnectionFactory來實現的。所以我們在配置一個MessageListenerContainer的時候有三個屬性必須指定,一個是表示從哪裏監聽的ConnectionFactory;一個是表示監聽什麼的Destination;一個是接收到消息以後進行消息處理的MessageListener。Spring一共爲我們提供了兩種類型的MessageListenerContainer,SimpleMessageListenerContainer和DefaultMessageListenerContainer。

SimpleMessageListenerContainer會在一開始的時候就創建一個會話session和消費者Consumer,並且會使用標準的JMS MessageConsumer.setMessageListener()方法註冊監聽器讓JMS提供者調用監聽器的回調函數。它不會動態的適應運行時需要和參與外部的事務管理。兼容性方面,它非常接近於獨立的JMS規範,但一般不兼容Java EE的JMS限制。

大多數情況下我們還是使用的DefaultMessageListenerContainer,跟SimpleMessageListenerContainer相比,DefaultMessageListenerContainer會動態的適應運行時需要,並且能夠參與外部的事務管理。它很好的平衡了對JMS提供者要求低、先進功能如事務參與和兼容Java EE環境。

定義處理消息的MessageListener

       要定義處理消息的MessageListener我們只需要實現JMS規範中的MessageListener接口就可以了。MessageListener接口中只有一個方法onMessage方法,當接收到消息的時候會自動調用該方法。


Java代碼 複製代碼 
  1. package com.tiantian.springintejms.listener;   
  2.     
  3. import javax.jms.JMSException;   
  4. import javax.jms.Message;   
  5. import javax.jms.MessageListener;   
  6. import javax.jms.TextMessage;   
  7.     
  8. public class ConsumerMessageListener implements MessageListener {   
  9.     
  10.     public void onMessage(Message message) {   
  11.         //這裏我們知道生產者發送的就是一個純文本消息,所以這裏可以直接進行強制轉換,或者直接把onMessage方法的參數改成Message的子類TextMessage  
  12.         TextMessage textMsg = (TextMessage) message;   
  13.         System.out.println("接收到一個純文本消息。");   
  14.         try {   
  15.             System.out.println("消息內容是:" + textMsg.getText());   
  16.         } catch (JMSException e) {   
  17.             e.printStackTrace();   
  18.         }   
  19.     }   
  20.     
  21. }  

       有了MessageListener之後我們就可以在Spring的配置文件中配置一個消息監聽容器了。

Xml代碼 複製代碼 
  1. <!--這個是隊列目的地-->  
  2. <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">  
  3.     <constructor-arg>  
  4.         <value>queue</value>  
  5.     </constructor-arg>  
  6. </bean>  
  7. <!-- 消息監聽器 -->  
  8. <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>       
  9.   
  10. <!-- 消息監聽容器 -->  
  11. <bean id="jmsContainer"        class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  12.     <property name="connectionFactory" ref="connectionFactory" />  
  13.     <property name="destination" ref="queueDestination" />  
  14.     <property name="messageListener" ref="consumerMessageListener" />  
  15. </bean>  

       我們可以看到我們定義了一個名叫queue的ActiveMQQueue目的地,我們的監聽器就是監聽了發送到這個目的地的消息。

       至此我們的生成者和消費者都配置完成了,這也就意味着我們的整合已經完成了。這個時候完整的Spring的配置文件應該是這樣的:

Xml代碼 複製代碼 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:jms="http://www.springframework.org/schema/jms"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  6.      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  7.      http://www.springframework.org/schema/context   
  8.      http://www.springframework.org/schema/context/spring-context-3.0.xsd   
  9.     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  10.     http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">  
  11.     
  12.     <context:component-scan base-package="com.tiantian" />  
  13.     
  14.     <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->  
  15.     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">  
  16.         <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->  
  17.         <property name="connectionFactory" ref="connectionFactory"/>  
  18.     </bean>  
  19.        
  20.     <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->  
  21.     <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">  
  22.         <property name="brokerURL" value="tcp://localhost:61616"/>  
  23.     </bean>  
  24.        
  25.     <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->  
  26.     <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">  
  27.         <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->  
  28.         <property name="targetConnectionFactory" ref="targetConnectionFactory"/>  
  29.     </bean>  
  30.        
  31.     <!--這個是隊列目的地-->  
  32.     <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">  
  33.         <constructor-arg>  
  34.             <value>queue</value>  
  35.         </constructor-arg>  
  36.     </bean>  
  37.     <!-- 消息監聽器 -->  
  38.     <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>  
  39.     <!-- 消息監聽容器 -->  
  40.     <bean id="jmsContainer"  
  41.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  42.         <property name="connectionFactory" ref="connectionFactory" />  
  43.         <property name="destination" ref="queueDestination" />  
  44.         <property name="messageListener" ref="consumerMessageListener" />  
  45.     </bean>  
  46. </beans>  

       接着我們來測試一下,看看我們的整合是否真的成功了,測試代碼如下:

 


Java代碼 複製代碼 
  1. package com.tiantian.springintejms.test;   
  2.     
  3. import javax.jms.Destination;   
  4.     
  5. import org.junit.Test;   
  6. import org.junit.runner.RunWith;   
  7. import org.springframework.beans.factory.annotation.Autowired;   
  8. import org.springframework.beans.factory.annotation.Qualifier;   
  9. import org.springframework.test.context.ContextConfiguration;   
  10. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;   
  11. import com.tiantian.springintejms.service.ProducerService;   
  12.     
  13. @RunWith(SpringJUnit4ClassRunner.class)   
  14. @ContextConfiguration("/applicationContext.xml")   
  15. public class ProducerConsumerTest {   
  16.     
  17.     @Autowired  
  18.     private ProducerService producerService;   
  19.     @Autowired  
  20.     @Qualifier("queueDestination")   
  21.     private Destination destination;   
  22.        
  23.     @Test  
  24.     public void testSend() {   
  25.         for (int i=0; i<2; i++) {   
  26.             producerService.sendMessage(destination, "你好,生產者!這是消息:" + (i+1));   
  27.         }   
  28.     }   
  29.        
  30. }  

       在上面的測試代碼中我們利用生產者發送了兩個消息,正常來說,消費者應該可以接收到這兩個消息。運行測試代碼後控制檯輸出如下:

 

 

       看,控制檯已經進行了正確的輸出,這說明我們的整合確實是已經成功了。

Spring整合JMS(二)——消息監聽器

 

3.1     消息監聽器MessageListener

       在Spring整合JMS的應用中我們在定義消息監聽器的時候一共可以定義三種類型的消息監聽器,分別是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。下面就分別來介紹一下這幾種類型的區別。

3.1.1 MessageListener

MessageListener是最原始的消息監聽器,它是JMS規範中定義的一個接口。其中定義了一個用於處理接收到的消息的onMessage方法,該方法只接收一個Message參數。我們前面在講配置消費者的時候用的消息監聽器就是MessageListener,代碼如下:

 

Java代碼 複製代碼 收藏代碼
  1. import javax.jms.JMSException;   
  2. import javax.jms.Message;   
  3. import javax.jms.MessageListener;   
  4. import javax.jms.TextMessage;   
  5.     
  6. public class ConsumerMessageListener implements MessageListener {   
  7.     
  8.     public void onMessage(Message message) {   
  9.         //這裏我們知道生產者發送的就是一個純文本消息,所以這裏可以直接進行強制轉換,或者直接把onMessage方法的參數改成Message的子類TextMessage  
  10.         TextMessage textMsg = (TextMessage) message;   
  11.         System.out.println("接收到一個純文本消息。");   
  12.         try {   
  13.             System.out.println("消息內容是:" + textMsg.getText());   
  14.         } catch (JMSException e) {   
  15.             e.printStackTrace();   
  16.         }   
  17.     }   
  18.     
  19. }  

3.1.2 SessionAwareMessageListener

SessionAwareMessageListener是Spring爲我們提供的,它不是標準的JMS MessageListener。MessageListener的設計只是純粹用來接收消息的,假如我們在使用MessageListener處理接收到的消息時我們需要發送一個消息通知對方我們已經收到這個消息了,那麼這個時候我們就需要在代碼裏面去重新獲取一個Connection或Session。SessionAwareMessageListener的設計就是爲了方便我們在接收到消息後發送一個回覆的消息,它同樣爲我們提供了一個處理接收到的消息的onMessage方法,但是這個方法可以同時接收兩個參數,一個是表示當前接收到的消息Message,另一個就是可以用來發送消息的Session對象。先來看一段代碼:

Java代碼 複製代碼 
  1. package com.tiantian.springintejms.listener;   
  2.     
  3. import javax.jms.Destination;   
  4. import javax.jms.JMSException;   
  5. import javax.jms.Message;   
  6. import javax.jms.MessageProducer;   
  7. import javax.jms.Session;   
  8. import javax.jms.TextMessage;   
  9.     
  10. import org.springframework.jms.listener.SessionAwareMessageListener;   
  11.     
  12. public class ConsumerSessionAwareMessageListener implements  
  13.         SessionAwareMessageListener<TextMessage> {   
  14.     
  15.     private Destination destination;   
  16.        
  17.     public void onMessage(TextMessage message, Session session) throws JMSException {   
  18.         System.out.println("收到一條消息");   
  19.         System.out.println("消息內容是:" + message.getText());   
  20.         MessageProducer producer = session.createProducer(destination);   
  21.         Message textMessage = session.createTextMessage("ConsumerSessionAwareMessageListener。。。");   
  22.         producer.send(textMessage);   
  23.     }   
  24.     
  25.     public Destination getDestination() {   
  26.         returndestination;   
  27.     }   
  28.     
  29.     public void setDestination(Destination destination) {   
  30.         this.destination = destination;   
  31.     }   
  32.     
  33. }  

       在上面代碼中我們定義了一個SessionAwareMessageListener,在這個Listener中我們在接收到了一個消息之後,利用對應的Session創建了一個到destination的生產者和對應的消息,然後利用創建好的生產者發送對應的消息。

       接着我們在Spring的配置文件中配置該消息監聽器將處理來自一個叫sessionAwareQueue的目的地的消息,並且往該MessageListener中通過set方法注入其屬性destination的值爲queueDestination。這樣當我們的SessionAwareMessageListener接收到消息之後就會往queueDestination發送一個消息。

Xml代碼 複製代碼 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:jms="http://www.springframework.org/schema/jms"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  6.      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  7.      http://www.springframework.org/schema/context   
  8.      http://www.springframework.org/schema/context/spring-context-3.0.xsd   
  9.     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
  10.     http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">  
  11.     
  12.     <context:component-scan base-package="com.tiantian" />    
  13.     <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->  
  14.     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">  
  15.         <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->  
  16.         <property name="connectionFactory" ref="connectionFactory"/>  
  17.     </bean>  
  18.        
  19.     <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->  
  20.     <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">  
  21.         <property name="brokerURL" value="tcp://localhost:61616"/>  
  22.     </bean>  
  23.        
  24.     <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->  
  25.     <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">  
  26.         <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->  
  27.         <property name="targetConnectionFactory" ref="targetConnectionFactory"/>  
  28.     </bean>  
  29.        
  30.     <!--這個是隊列目的地-->  
  31.     <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">  
  32.         <constructor-arg>  
  33.             <value>queue</value>  
  34.         </constructor-arg>  
  35.     </bean>  
  36.     <!--這個是sessionAwareQueue目的地-->  
  37.     <bean id="sessionAwareQueue" class="org.apache.activemq.command.ActiveMQQueue">  
  38.         <constructor-arg>  
  39.             <value>sessionAwareQueue</value>  
  40.         </constructor-arg>  
  41.     </bean>  
  42.     <!-- 消息監聽器 -->  
  43.     <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>  
  44.     <!-- 可以獲取session的MessageListener -->  
  45.     <bean id="consumerSessionAwareMessageListener" class="com.tiantian.springintejms.listener.ConsumerSessionAwareMessageListener">  
  46.         <property name="destination" ref="queueDestination"/>  
  47.     </bean>  
  48.     <!-- 消息監聽容器 -->  
  49.     <bean id="jmsContainer"        class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  50.         <property name="connectionFactory" ref="connectionFactory" />  
  51.         <property name="destination" ref="queueDestination" />  
  52.         <property name="messageListener" ref="consumerMessageListener" />  
  53.     </bean>  
  54.        
  55.     <bean id="sessionAwareListenerContainer"  
  56.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  57.         <property name="connectionFactory" ref="connectionFactory" />  
  58.         <property name="destination" ref="sessionAwareQueue" />  
  59.         <property name="messageListener" ref="consumerSessionAwareMessageListener" />  
  60.     </bean>  
  61. </beans>  

       接着我們來做一個測試,測試代碼如下:

Java代碼 複製代碼 
  1. @RunWith(SpringJUnit4ClassRunner.class)   
  2. @ContextConfiguration("/applicationContext.xml")   
  3. public class ProducerConsumerTest {   
  4.     
  5.     @Autowired  
  6.     private ProducerService producerService;   
  7.     @Autowired  
  8.     @Qualifier("sessionAwareQueue")   
  9.     private Destination sessionAwareQueue;   
  10.        
  11.     @Test  
  12.     public void testSessionAwareMessageListener() {   
  13.         producerService.sendMessage(sessionAwareQueue, "測試SessionAwareMessageListener");   
  14.     }   
  15.        
  16. }  

       在上述測試代碼中,我們通過前面定義好的生產者往我們定義好的SessionAwareMessageListener監聽的sessionAwareQueue發送了一個消息。程序運行之後控制檯輸出如下:



 

 

       這說明我們已經成功的往sessionAwareQueue發送了一條純文本消息,消息會被ConsumerSessionAwareMessageListener的onMessage方法進行處理,在onMessage方法中ConsumerSessionAwareMessageListener就是簡單的把接收到的純文本信息的內容打印出來了,之後再往queueDestination發送了一個純文本消息,消息內容是“ConsumerSessionAwareMessageListener…”,該消息隨後就被ConsumerMessageListener處理了,根據我們的定義,在ConsumerMessageListener中也只是簡單的打印了一下接收到的消息內容。

3.1.3 MessageListenerAdapter

MessageListenerAdapter類實現了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是將接收到的消息進行類型轉換,然後通過反射的形式把它交給一個普通的Java類進行處理。

       MessageListenerAdapter會把接收到的消息做如下轉換:

       TextMessage轉換爲String對象;

       BytesMessage轉換爲byte數組;

       MapMessage轉換爲Map對象;

       ObjectMessage轉換爲對應的Serializable對象。

       既然前面說了MessageListenerAdapter會把接收到的消息做一個類型轉換,然後利用反射把它交給真正的目標處理器——一個普通的Java類進行處理(如果真正的目標處理器是一個MessageListener或者是一個SessionAwareMessageListener,那麼Spring將直接使用接收到的Message對象作爲參數調用它們的onMessage方法,而不會再利用反射去進行調用),那麼我們在定義一個MessageListenerAdapter的時候就需要爲它指定這樣一個目標類。這個目標類我們可以通過MessageListenerAdapter的構造方法參數指定,如:

Xml代碼 複製代碼 
  1. <!-- 消息監聽適配器 -->  
  2.     <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">  
  3.         <constructor-arg>  
  4.             <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>  
  5.         </constructor-arg>  
  6.     </bean>  

 

       也可以通過它的delegate屬性來指定,如:

Xml代碼 複製代碼 
  1. <!-- 消息監聽適配器 -->  
  2.     <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">  
  3.         <property name="delegate">  
  4.             <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>  
  5.         </property>  
  6.         <property name="defaultListenerMethod" value="receiveMessage"/>  
  7.     </bean>  

       前面說了如果我們指定的這個目標處理器是一個MessageListener或者是一個SessionAwareMessageListener的時候Spring將直接利用接收到的Message對象作爲方法參數調用它們的onMessage方法。但是如果指定的目標處理器是一個普通的Java類時Spring將利用Message進行了類型轉換之後的對象作爲參數通過反射去調用真正的目標處理器的處理方法,那麼Spring是如何知道該調用哪個方法呢?這是通過MessageListenerAdapter的defaultListenerMethod屬性來決定的,當我們沒有指定該屬性時,Spring會默認調用目標處理器的handleMessage方法。

       接下來我們來看一個示例,假設我們有一個普通的Java類ConsumerListener,其對應有兩個方法,handleMessage和receiveMessage,其代碼如下:

Java代碼 複製代碼 
  1. package com.tiantian.springintejms.listener;   
  2.     
  3. public class ConsumerListener {   
  4.     
  5.     public void handleMessage(String message) {   
  6.         System.out.println("ConsumerListener通過handleMessage接收到一個純文本消息,消息內容是:" + message);   
  7.     }   
  8.        
  9.     public void receiveMessage(String message) {   
  10.         System.out.println("ConsumerListener通過receiveMessage接收到一個純文本消息,消息內容是:" + message);   
  11.     }   
  12.        
  13. }  

       假設我們要把它作爲一個消息監聽器來監聽發送到adapterQueue的消息,這個時候我們就可以定義一個對應的MessageListenerAdapter來把它當做一個MessageListener使用。

Xml代碼 複製代碼 
  1. <!-- 消息監聽適配器 -->  
  2. <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">  
  3.     <property name="delegate">  
  4.         <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>  
  5.     </property>  
  6.     <property name="defaultListenerMethod" value="receiveMessage"/>  
  7. </bean>  

       當然,有了MessageListener之後我們還需要配置其對應的MessageListenerContainer,這裏配置如下:

Xml代碼 複製代碼 
  1. <!-- 消息監聽適配器對應的監聽容器 -->  
  2. <bean id="messageListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  3.     <property name="connectionFactory" ref="connectionFactory"/>  
  4.     <property name="destination" ref="adapterQueue"/>  
  5.     <property name="messageListener" ref="messageListenerAdapter"/><!-- 使用MessageListenerAdapter來作爲消息監聽器 -->  
  6. </bean>  

       在上面的MessageListenerAdapter中我們指定了其defaultListenerMethod屬性的值爲receiveMessage,所以當MessageListenerAdapter接收到消息之後會自動的調用我們指定的ConsumerListener的receiveMessage方法。

       針對於上述代碼我們定義測試代碼如下:

Java代碼 複製代碼 
  1. package com.tiantian.springintejms.test;   
  2.     
  3. import javax.jms.Destination;   
  4.     
  5. import org.junit.Test;   
  6. import org.junit.runner.RunWith;   
  7. import org.springframework.beans.factory.annotation.Autowired;   
  8. import org.springframework.beans.factory.annotation.Qualifier;   
  9. import org.springframework.test.context.ContextConfiguration;   
  10. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;   
  11.     
  12. import com.tiantian.springintejms.service.ProducerService;   
  13.     
  14. @RunWith(SpringJUnit4ClassRunner.class)   
  15. @ContextConfiguration("/applicationContext.xml")   
  16. public class ProducerConsumerTest {   
  17.   
  18.     @Autowired  
  19.     @Qualifier("adapterQueue")   
  20.     private Destination adapterQueue;   
  21.   
  22.     @Test  
  23.     public void testMessageListenerAdapter() {   
  24.         producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");   
  25.     }   
  26.        
  27. }  

       這時候我們會看到控制檯輸出如下:



       
如果我們指定MessageListenerAdapter的defaultListenerMethod屬性,那麼在運行上述代碼時控制檯會輸出如下結果:



       
MessageListenerAdapter除了會自動的把一個普通Java類當做MessageListener來處理接收到的消息之外,其另外一個主要的功能是可以自動的發送返回消息

     當我們用於處理接收到的消息的方法的返回值不爲空的時候,Spring會自動將它封裝爲一個JMS Message,然後自動進行回覆。那麼這個時候這個回覆消息將發送到哪裏呢?這主要有兩種方式可以指定。
       第一,可以通過發送的Message的setJMSReplyTo方法指定該消息對應的回覆消息的目的地。這裏我們把我們的生產者發送消息的代碼做一下修改,在發送消息之前先指定該消息對應的回覆目的地爲一個叫responseQueue的隊列目的地,具體代碼如下所示:

Java代碼 複製代碼 
  1. package com.tiantian.springintejms.service.impl;   
  2.     
  3. import javax.jms.Destination;   
  4. import javax.jms.JMSException;   
  5. import javax.jms.Message;   
  6. import javax.jms.Session;   
  7. import javax.jms.TextMessage;   
  8.     
  9. import org.springframework.beans.factory.annotation.Autowired;   
  10. import org.springframework.beans.factory.annotation.Qualifier;   
  11. import org.springframework.jms.core.JmsTemplate;   
  12. import org.springframework.jms.core.MessageCreator;   
  13. import org.springframework.stereotype.Component;   
  14.     
  15. import com.tiantian.springintejms.service.ProducerService;   
  16.     
  17. @Component  
  18. public class ProducerServiceImpl implements ProducerService {    
  19.   
  20.     @Autowired  
  21.     private JmsTemplate jmsTemplate;   
  22.   
  23.     @Autowired  
  24.     @Qualifier("responseQueue")   
  25.     private Destination responseDestination;   
  26.        
  27.     public void sendMessage(Destination destination, final String message) {   
  28.         System.out.println("---------------生產者發送消息-----------------");   
  29.         System.out.println("---------------生產者發了一個消息:" + message);   
  30.         jmsTemplate.send(destination, new MessageCreator() {   
  31.             public Message createMessage(Session session) throws JMSException {   
  32.                 TextMessage textMessage = session.createTextMessage(message);   
  33.                 textMessage.setJMSReplyTo(responseDestination);   
  34.                 return textMessage;   
  35.             }   
  36.         });   
  37.     }   
  38.     
  39. }  

       接着定義一個叫responseQueue的隊列目的地及其對應的消息監聽器和監聽容器。

Xml代碼 複製代碼 
  1. <!-- 用於測試消息回覆的 -->  
  2. <bean id="responseQueue" class="org.apache.activemq.command.ActiveMQQueue">  
  3.     <constructor-arg>  
  4.         <value>responseQueue</value>  
  5.     </constructor-arg>  
  6. </bean>  
  7.   
  8. <!-- responseQueue對應的監聽器 -->  
  9. <bean id="responseQueueListener" class="com.tiantian.springintejms.listener.ResponseQueueListener"/>  
  10.   
  11. <!-- responseQueue對應的監聽容器 -->  
  12. <bean id="responseQueueMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  13.     <property name="connectionFactory" ref="connectionFactory"/>  
  14.     <property name="destination" ref="responseQueue"/>  
  15.     <property name="messageListener" ref="responseQueueListener"/>  
  16. </bean>  

       ResponseQueueListener的定義如下所示:

Java代碼 複製代碼 
  1. public class ResponseQueueListener implements MessageListener {   
  2.     
  3.     public void onMessage(Message message) {   
  4.         if (message instanceof TextMessage) {   
  5.             TextMessage textMessage = (TextMessage) message;   
  6.             try {   
  7.                 System.out.println("接收到發送到responseQueue的一個文本消息,內容是:" + textMessage.getText());   
  8.             } catch (JMSException e) {   
  9.                 e.printStackTrace();   
  10.             }   
  11.         }   
  12.     }   
  13.     
  14. }  

       接着我們運行我們的測試代碼,利用生產者往我們定義好的MessageListenerAdapter負責處理的adapterQueue目的地發送一個消息。測試代碼如下所示:

Java代碼 複製代碼 
  1. @RunWith(SpringJUnit4ClassRunner.class)   
  2. @ContextConfiguration("/applicationContext.xml")   
  3. public class ProducerConsumerTest {   
  4.     
  5.     @Autowired  
  6.     private ProducerService producerService;   
  7.   
  8.     @Qualifier("adapterQueue")   
  9.     @Autowired  
  10.     private Destination adapterQueue;      
  11.   
  12.     @Test  
  13.     public void testMessageListenerAdapter() {   
  14.         producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");   
  15.     }   
  16.        
  17. }  

       運行上述測試代碼之後,控制檯輸出如下:

 



        這說明我們的生產者發送消息被MessageListenerAdapter處理之後,MessageListenerAdapter確實把監聽器的返回內容封裝成一個Message往原Message通過setJMSReplyTo方法指定的回覆目的地發送了一個消息。對於MessageListenerAdapter對應的監聽器處理方法返回的是一個null值或者返回類型是void的情況,MessageListenerAdapter是不會自動進行消息的回覆的,有興趣的網友可以自己測試一下。

       第二,通過MessageListenerAdapter的defaultResponseDestination屬性來指定。這裏我們也來做一個測試,首先維持生產者發送消息的代碼不變,即發送消息前不通過Message的setJMSReplyTo方法指定消息的回覆目的地;接着我們在定義MessageListenerAdapter的時候通過其defaultResponseDestination屬性指定其默認的回覆目的地是“defaultResponseQueue”,並定義defaultResponseQueue對應的消息監聽器和消息監聽容器。

Xml代碼 複製代碼 
  1. <!-- 消息監聽適配器 -->  
  2. <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">  
  3.     <!-- <constructor-arg>  
  4.         <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>  
  5.     </constructor-arg> -->  
  6.     <property name="delegate">  
  7.         <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>  
  8.     </property>  
  9.     <property name="defaultListenerMethod" value="receiveMessage"/>  
  10.     <property name="defaultResponseDestination" ref="defaultResponseQueue"/>  
  11. </bean>  
  12.   
  13. <!-- 消息監聽適配器對應的監聽容器 -->  
  14. <bean id="messageListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  15.     <property name="connectionFactory" ref="connectionFactory"/>  
  16.     <property name="destination" ref="adapterQueue"/>  
  17.     <property name="messageListener" ref="messageListenerAdapter"/><!-- 使用MessageListenerAdapter來作爲消息監聽器 -->  
  18. </bean>  
  19.   
  20. !-- 默認的消息回覆隊列 -->  
  21. <bean id="defaultResponseQueue" class="org.apache.activemq.command.ActiveMQQueue">  
  22.     <constructor-arg>  
  23.         <value>defaultResponseQueue</value>  
  24.     </constructor-arg>  
  25. </bean>  
  26.   
  27. <!-- defaultResponseQueue對應的監聽器 -->  
  28. <bean id="defaultResponseQueueListener" class="com.tiantian.springintejms.listener.DefaultResponseQueueListener"/>  
  29.   
  30. <!-- defaultResponseQueue對應的監聽容器 -->  
  31. <bean id="defaultResponseQueueMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">  
  32.     <property name="connectionFactory" ref="connectionFactory"/>  
  33.     <property name="destination" ref="defaultResponseQueue"/>  
  34.     <property name="messageListener" ref="defaultResponseQueueListener"/>  
  35. </bean>  

       DefaultResponseQueueListener的代碼如下所示:

Java代碼 複製代碼 
  1. package com.tiantian.springintejms.listener;   
  2.     
  3. import javax.jms.JMSException;   
  4. import javax.jms.Message;   
  5. import javax.jms.MessageListener;   
  6. import javax.jms.TextMessage;   
  7.     
  8. public class DefaultResponseQueueListener implements MessageListener {   
  9.     
  10.     public void onMessage(Message message) {   
  11.         if (message instanceof TextMessage) {   
  12.             TextMessage textMessage = (TextMessage) message;   
  13.             try {   
  14.                 System.out.println("DefaultResponseQueueListener接收到發送到defaultResponseQueue的一個文本消息,內容是:" + textMessage.getText());   
  15.             } catch (JMSException e) {   
  16.                 e.printStackTrace();   
  17.             }   
  18.         }   
  19.     }   
  20.     
  21. }  

       這時候運行如下測試代碼:

Java代碼 複製代碼 
  1. @Test  
  2. public void testMessageListenerAdapter() {   
  3.     producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");   
  4. }  
    @Test    public void testMessageListenerAdapter() {        producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");    }

 

       控制檯將輸出如下內容:

 



        這說明MessageListenerAdapter會自動把真正的消息處理器返回的非空內容封裝成一個Message發送回覆消息到通過defaultResponseDestination屬性指定的默認消息回覆目的地。

       既然我們可以通過兩種方式來指定MessageListenerAdapter自動發送回覆消息的目的地,那麼當我們兩種方式都指定了而且它們的目的地還不一樣的時候會怎麼發送呢?是兩個都發還是隻發其中的一個呢?關於這部分的測試我這裏就不贅述了,有興趣的網友可以自己進行。這裏我可以直接的告訴大家,當兩種方式都指定了消息的回覆目的地的時候使用發送消息的setJMSReplyTo方法指定的目的地將具有較高的優先級,MessageListenerAdapter將只往該方法指定的消息回覆目的地發送回覆消息。

發佈了90 篇原創文章 · 獲贊 19 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章