1.3 消息監聽器MessageListener
在Spring整合JMS的應用中我們在定義消息監聽器的時候一共可以定義三種類型的消息監聽器,分別是MessageListener、SessionAwareMessageListener和MessageListenerAdapter。下面就分別來介紹一下這幾種類型的區別。
1.3.1 MessageListener
MessageListener是最原始的消息監聽器,它是JMS規範中定義的一個接口。其中定義了一個用於處理接收到的消息的onMessage方法,該方法只接收一個Message參數。我們前面在講配置消費者的時候用的消息監聽器就是MessageListener,代碼如下:
- import javax.jms.JMSException;
- import javax.jms.Message;
- import javax.jms.MessageListener;
- import javax.jms.TextMessage;
- public class ConsumerMessageListener implements MessageListener {
- public void onMessage(Message message) {
- //這裏我們知道生產者發送的就是一個純文本消息,所以這裏可以直接進行強制轉換,或者直接把onMessage方法的參數改成Message的子類TextMessage
- TextMessage textMsg = (TextMessage) message;
- System.out.println("接收到一個純文本消息。");
- try {
- System.out.println("消息內容是:" + textMsg.getText());
- } catch (JMSException e) {
- e.printStackTrace();
- }
- }
- }
1.3.2 SessionAwareMessageListener
SessionAwareMessageListener是Spring爲我們提供的,它不是標準的JMS MessageListener。MessageListener的設計只是純粹用來接收消息的,假如我們在使用MessageListener處理接收到的消息時我們需要發送一個消息通知對方我們已經收到這個消息了,那麼這個時候我們就需要在代碼裏面去重新獲取一個Connection或Session。SessionAwareMessageListener的設計就是爲了方便我們在接收到消息後發送一個回覆的消息,它同樣爲我們提供了一個處理接收到的消息的onMessage方法,但是這個方法可以同時接收兩個參數,一個是表示當前接收到的消息Message,另一個就是可以用來發送消息的Session對象。先來看一段代碼:
- package com.tiantian.springintejms.listener;
- import javax.jms.Destination;
- import javax.jms.JMSException;
- import javax.jms.Message;
- import javax.jms.MessageProducer;
- import javax.jms.Session;
- import javax.jms.TextMessage;
- import org.springframework.jms.listener.SessionAwareMessageListener;
- public class ConsumerSessionAwareMessageListener implements
- SessionAwareMessageListener<TextMessage> {
- private Destination destination;
- public void onMessage(TextMessage message, Session session) throws JMSException {
- System.out.println("收到一條消息");
- System.out.println("消息內容是:" + message.getText());
- MessageProducer producer = session.createProducer(destination);
- Message textMessage = session.createTextMessage("ConsumerSessionAwareMessageListener。。。");
- producer.send(textMessage);
- }
- public Destination getDestination() {
- returndestination;
- }
- public void setDestination(Destination destination) {
- this.destination = destination;
- }
- }
在上面代碼中我們定義了一個SessionAwareMessageListener,在這個Listener中我們在接收到了一個消息之後,利用對應的Session創建了一個到destination的生產者和對應的消息,然後利用創建好的生產者發送對應的消息。
接着我們在Spring的配置文件中配置該消息監聽器將處理來自一個叫sessionAwareQueue的目的地的消息,並且往該MessageListener中通過set方法注入其屬性destination的值爲queueDestination。這樣當我們的SessionAwareMessageListener接收到消息之後就會往queueDestination發送一個消息。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
- xmlns:jms="http://www.springframework.org/schema/jms"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">
- <context:component-scan base-package="com.tiantian" />
- <!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->
- <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
- <!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
- <property name="connectionFactory" ref="connectionFactory"/>
- </bean>
- <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
- <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
- <property name="brokerURL" value="tcp://localhost:61616"/>
- </bean>
- <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->
- <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
- <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->
- <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
- </bean>
- <!--這個是隊列目的地-->
- <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>queue</value>
- </constructor-arg>
- </bean>
- <!--這個是sessionAwareQueue目的地-->
- <bean id="sessionAwareQueue" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>sessionAwareQueue</value>
- </constructor-arg>
- </bean>
- <!-- 消息監聽器 -->
- <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>
- <!-- 可以獲取session的MessageListener -->
- <bean id="consumerSessionAwareMessageListener" class="com.tiantian.springintejms.listener.ConsumerSessionAwareMessageListener">
- <property name="destination" ref="queueDestination"/>
- </bean>
- <!-- 消息監聽容器 -->
- <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory" />
- <property name="destination" ref="queueDestination" />
- <property name="messageListener" ref="consumerMessageListener" />
- </bean>
- <bean id="sessionAwareListenerContainer"
- class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory" />
- <property name="destination" ref="sessionAwareQueue" />
- <property name="messageListener" ref="consumerSessionAwareMessageListener" />
- </bean>
- </beans>
接着我們來做一個測試,測試代碼如下:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("/applicationContext.xml")
- public class ProducerConsumerTest {
- @Autowired
- private ProducerService producerService;
- @Autowired
- @Qualifier("sessionAwareQueue")
- private Destination sessionAwareQueue;
- @Test
- public void testSessionAwareMessageListener() {
- producerService.sendMessage(sessionAwareQueue, "測試SessionAwareMessageListener");
- }
- }
在上述測試代碼中,我們通過前面定義好的生產者往我們定義好的SessionAwareMessageListener監聽的sessionAwareQueue發送了一個消息。程序運行之後控制檯輸出如下:
這說明我們已經成功的往sessionAwareQueue發送了一條純文本消息,消息會被ConsumerSessionAwareMessageListener的onMessage方法進行處理,在onMessage方法中ConsumerSessionAwareMessageListener就是簡單的把接收到的純文本信息的內容打印出來了,之後再往queueDestination發送了一個純文本消息,消息內容是“ConsumerSessionAwareMessageListener…”,該消息隨後就被ConsumerMessageListener處理了,根據我們的定義,在ConsumerMessageListener中也只是簡單的打印了一下接收到的消息內容。
1.3.3 MessageListenerAdapter
MessageListenerAdapter類實現了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是將接收到的消息進行類型轉換,然後通過反射的形式把它交給一個普通的Java類進行處理。
MessageListenerAdapter會把接收到的消息做如下轉換:
TextMessage轉換爲String對象;
BytesMessage轉換爲byte數組;
MapMessage轉換爲Map對象;
ObjectMessage轉換爲對應的Serializable對象。
既然前面說了MessageListenerAdapter會把接收到的消息做一個類型轉換,然後利用反射把它交給真正的目標處理器——一個普通的Java類進行處理(如果真正的目標處理器是一個MessageListener或者是一個SessionAwareMessageListener,那麼Spring將直接使用接收到的Message對象作爲參數調用它們的onMessage方法,而不會再利用反射去進行調用),那麼我們在定義一個MessageListenerAdapter的時候就需要爲它指定這樣一個目標類。這個目標類我們可以通過MessageListenerAdapter的構造方法參數指定,如:
也可以通過它的delegate屬性來指定,如:
- <!-- 消息監聽適配器 -->
- <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
- <property name="delegate">
- <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>
- </property>
- <property name="defaultListenerMethod" value="receiveMessage"/>
- </bean>
前面說了如果我們指定的這個目標處理器是一個MessageListener或者是一個SessionAwareMessageListener的時候Spring將直接利用接收到的Message對象作爲方法參數調用它們的onMessage方法。但是如果指定的目標處理器是一個普通的Java類時Spring將利用Message進行了類型轉換之後的對象作爲參數通過反射去調用真正的目標處理器的處理方法,那麼Spring是如何知道該調用哪個方法呢?這是通過MessageListenerAdapter的defaultListenerMethod屬性來決定的,當我們沒有指定該屬性時,Spring會默認調用目標處理器的handleMessage方法。
接下來我們來看一個示例,假設我們有一個普通的Java類ConsumerListener,其對應有兩個方法,handleMessage和receiveMessage,其代碼如下:
- package com.tiantian.springintejms.listener;
- public class ConsumerListener {
- public void handleMessage(String message) {
- System.out.println("ConsumerListener通過handleMessage接收到一個純文本消息,消息內容是:" + message);
- }
- public void receiveMessage(String message) {
- System.out.println("ConsumerListener通過receiveMessage接收到一個純文本消息,消息內容是:" + message);
- }
- }
假設我們要把它作爲一個消息監聽器來監聽發送到adapterQueue的消息,這個時候我們就可以定義一個對應的MessageListenerAdapter來把它當做一個MessageListener使用。
- <!-- 消息監聽適配器 -->
- <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
- <property name="delegate">
- <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>
- </property>
- <property name="defaultListenerMethod" value="receiveMessage"/>
- </bean>
當然,有了MessageListener之後我們還需要配置其對應的MessageListenerContainer,這裏配置如下:
- <!-- 消息監聽適配器對應的監聽容器 -->
- <bean id="messageListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory"/>
- <property name="destination" ref="adapterQueue"/>
- <property name="messageListener" ref="messageListenerAdapter"/><!-- 使用MessageListenerAdapter來作爲消息監聽器 -->
- </bean>
- <!-- 用於測試消息監聽適配器的隊列目的地 -->
- <bean id="adapterQueue" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>adapterQueue</value>
- </constructor-arg>
- </bean>
在上面的MessageListenerAdapter中我們指定了其defaultListenerMethod屬性的值爲receiveMessage,所以當MessageListenerAdapter接收到消息之後會自動的調用我們指定的ConsumerListener的receiveMessage方法。
針對於上述代碼我們定義測試代碼如下:
- package com.tiantian.springintejms.test;
- import javax.jms.Destination;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
- import com.tiantian.springintejms.service.ProducerService;
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("/applicationContext.xml")
- public class ProducerConsumerTest {
- @Autowired
- @Qualifier("adapterQueue")
- private Destination adapterQueue;
- @Test
- public void testMessageListenerAdapter() {
- producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");
- }
- }
這時候我們會看到控制檯輸出如下:
如果我們不指定MessageListenerAdapter的defaultListenerMethod屬性,那麼在運行上述代碼時控制檯會輸出如下結果:
MessageListenerAdapter除了會自動的把一個普通Java類當做MessageListener來處理接收到的消息之外,其另外一個主要的功能是可以自動的發送返回消息。
當我們用於處理接收到的消息的方法的返回值不爲空的時候,Spring會自動將它封裝爲一個JMS Message,然後自動進行回覆。那麼這個時候這個回覆消息將發送到哪裏呢?這主要有兩種方式可以指定。
第一,可以通過發送的Message的setJMSReplyTo方法指定該消息對應的回覆消息的目的地。這裏我們把我們的生產者發送消息的代碼做一下修改,在發送消息之前先指定該消息對應的回覆目的地爲一個叫responseQueue的隊列目的地,具體代碼如下所示:
- package com.tiantian.springintejms.service.impl;
- import javax.jms.Destination;
- import javax.jms.JMSException;
- import javax.jms.Message;
- import javax.jms.Session;
- import javax.jms.TextMessage;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.jms.core.JmsTemplate;
- import org.springframework.jms.core.MessageCreator;
- import org.springframework.stereotype.Component;
- import com.tiantian.springintejms.service.ProducerService;
- @Component
- public class ProducerServiceImpl implements ProducerService {
- @Autowired
- private JmsTemplate jmsTemplate;
- @Autowired
- @Qualifier("responseQueue")
- private Destination responseDestination;
- 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 {
- TextMessage textMessage = session.createTextMessage(message);
- textMessage.setJMSReplyTo(responseDestination);
- return textMessage;
- }
- });
- }
- }
接着定義一個叫responseQueue的隊列目的地及其對應的消息監聽器和監聽容器。
- <!-- 用於測試消息回覆的 -->
- <bean id="responseQueue" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>responseQueue</value>
- </constructor-arg>
- </bean>
- <!-- responseQueue對應的監聽器 -->
- <bean id="responseQueueListener" class="com.tiantian.springintejms.listener.ResponseQueueListener"/>
- <!-- responseQueue對應的監聽容器 -->
- <bean id="responseQueueMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory"/>
- <property name="destination" ref="responseQueue"/>
- <property name="messageListener" ref="responseQueueListener"/>
- </bean>
ResponseQueueListener的定義如下所示:
- public class ResponseQueueListener implements MessageListener {
- public void onMessage(Message message) {
- if (message instanceof TextMessage) {
- TextMessage textMessage = (TextMessage) message;
- try {
- System.out.println("接收到發送到responseQueue的一個文本消息,內容是:" + textMessage.getText());
- } catch (JMSException e) {
- e.printStackTrace();
- }
- }
- }
- }
接着把我們接收消息的ConsumerListener的receiveMessage方法改爲如下:
我們可以看到在上述負責接收消息的receiveMessage方法有一個非空的返回值。
接着我們運行我們的測試代碼,利用生產者往我們定義好的MessageListenerAdapter負責處理的adapterQueue目的地發送一個消息。測試代碼如下所示:
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("/applicationContext.xml")
- public class ProducerConsumerTest {
- @Autowired
- private ProducerService producerService;
- @Qualifier("adapterQueue")
- @Autowired
- private Destination adapterQueue;
- @Test
- public void testMessageListenerAdapter() {
- producerService.sendMessage(adapterQueue, "測試MessageListenerAdapter");
- }
- }
運行上述測試代碼之後,控制檯輸出如下:
這說明我們的生產者發送消息被MessageListenerAdapter處理之後,MessageListenerAdapter確實把監聽器的返回內容封裝成一個Message往原Message通過setJMSReplyTo方法指定的回覆目的地發送了一個消息。對於MessageListenerAdapter對應的監聽器處理方法返回的是一個null值或者返回類型是void的情況,MessageListenerAdapter是不會自動進行消息的回覆的,有興趣的網友可以自己測試一下。
第二,通過MessageListenerAdapter的defaultResponseDestination屬性來指定。這裏我們也來做一個測試,首先維持生產者發送消息的代碼不變,即發送消息前不通過Message的setJMSReplyTo方法指定消息的回覆目的地;接着我們在定義MessageListenerAdapter的時候通過其defaultResponseDestination屬性指定其默認的回覆目的地是“defaultResponseQueue”,並定義defaultResponseQueue對應的消息監聽器和消息監聽容器。
- <!-- 消息監聽適配器 -->
- <bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
- <!-- <constructor-arg>
- <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>
- </constructor-arg> -->
- <property name="delegate">
- <bean class="com.tiantian.springintejms.listener.ConsumerListener"/>
- </property>
- <property name="defaultListenerMethod" value="receiveMessage"/>
- <property name="defaultResponseDestination" ref="defaultResponseQueue"/>
- </bean>
- <!-- 消息監聽適配器對應的監聽容器 -->
- <bean id="messageListenerAdapterContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory"/>
- <property name="destination" ref="adapterQueue"/>
- <property name="messageListener" ref="messageListenerAdapter"/><!-- 使用MessageListenerAdapter來作爲消息監聽器 -->
- </bean>
- !-- 默認的消息回覆隊列 -->
- <bean id="defaultResponseQueue" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>defaultResponseQueue</value>
- </constructor-arg>
- </bean>
- <!-- defaultResponseQueue對應的監聽器 -->
- <bean id="defaultResponseQueueListener" class="com.tiantian.springintejms.listener.DefaultResponseQueueListener"/>
- <!-- defaultResponseQueue對應的監聽容器 -->
- <bean id="defaultResponseQueueMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
- <property name="connectionFactory" ref="connectionFactory"/>
- <property name="destination" ref="defaultResponseQueue"/>
- <property name="messageListener" ref="defaultResponseQueueListener"/>
- </bean>
DefaultResponseQueueListener的代碼如下所示:
- package com.tiantian.springintejms.listener;
- import javax.jms.JMSException;
- import javax.jms.Message;
- import javax.jms.MessageListener;
- import javax.jms.TextMessage;
- public class DefaultResponseQueueListener implements MessageListener {
- public void onMessage(Message message) {
- if (message instanceof TextMessage) {
- TextMessage textMessage = (TextMessage) message;
- try {
- System.out.println("DefaultResponseQueueListener接收到發送到defaultResponseQueue的一個文本消息,內容是:" + textMessage.getText());
- } catch (JMSException e) {
- e.printStackTrace();
- }
- }
- }
- }
這時候運行如下測試代碼:
控制檯將輸出如下內容:
這說明MessageListenerAdapter會自動把真正的消息處理器返回的非空內容封裝成一個Message發送回覆消息到通過defaultResponseDestination屬性指定的默認消息回覆目的地。
既然我們可以通過兩種方式來指定MessageListenerAdapter自動發送回覆消息的目的地,那麼當我們兩種方式都指定了而且它們的目的地還不一樣的時候會怎麼發送呢?是兩個都發還是隻發其中的一個呢?關於這部分的測試我這裏就不贅述了,有興趣的網友可以自己進行。這裏我可以直接的告訴大家,當兩種方式都指定了消息的回覆目的地的時候使用發送消息的setJMSReplyTo方法指定的目的地將具有較高的優先級,MessageListenerAdapter將只往該方法指定的消息回覆目的地發送回覆消息。