1.1 JMS簡介
JMS的全稱是Java Message Service,即Java消息服務。它主要用於在生產者和消費者之間進行消息傳遞,生產者負責產生消息,而消費者負責接收消息。把它應用到實際的業務需求中的話我們可以在特定的時候利用生產者生成一消息,並進行發送,對應的消費者在接收到對應的消息後去完成對應的業務邏輯。對於消息的傳遞有兩種類型,一種是點對點的,即一個生產者和一個消費者一一對應;另一種是發佈/訂閱模式,即一個生產者產生消息並進行發送後,可以由多個消費者進行接收。
1.2 Spring整合JMS
對JMS做了一個簡要介紹之後,接下來就講一下Spring整合JMS的具體過程。JMS只是一個標準,真正在使用它的時候我們需要有它的具體實現,這裏我們就使用Apache的activeMQ來作爲它的實現。所使用的依賴利用Maven來進行管理,具體依賴如下:
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.10</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring-version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jms</artifactId>
- <version>${spring-version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring-version}</version>
- </dependency>
- <dependency>
- <groupId>javax.annotation</groupId>
- <artifactId>jsr250-api</artifactId>
- <version>1.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.activemq</groupId>
- <artifactId>activemq-core</artifactId>
- <version>5.7.0</version>
- </dependency>
- </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來作爲示例。
這樣就定義好產生JMS服務器鏈接的ConnectionFactory了嗎?答案是非也。Spring提供的ConnectionFactory只是Spring用於管理ConnectionFactory的,真正產生到JMS服務器鏈接的ConnectionFactory還得是由JMS服務廠商提供,並且需要把它注入到Spring提供的ConnectionFactory中。我們這裏使用的是ActiveMQ實現的JMS,所以在我們這裏真正的可以產生Connection的就應該是由ActiveMQ提供的ConnectionFactory。所以定義一個ConnectionFactory的完整代碼應該如下所示:
- <!-- 真正可以產生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>
ActiveMQ爲我們提供了一個PooledConnectionFactory,通過往裏面注入一個ActiveMQConnectionFactory可以用來將Connection、Session和MessageProducer池化,這樣可以大大的減少我們的資源消耗。當使用PooledConnectionFactory時,我們在定義一個ConnectionFactory時應該是如下定義:
- <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
- <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
- <property name="brokerURL" value="tcp://localhost:61616"/>
- </bean>
- <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory">
- <property name="connectionFactory" ref="targetConnectionFactory"/>
- <property name="maxConnections" value="10"/>
- </bean>
- <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
- <property name="targetConnectionFactory" ref="pooledConnectionFactory"/>
- </bean>
1.2.3配置生產者
配置好ConnectionFactory之後我們就需要配置生產者。生產者負責產生消息併發送到JMS服務器,這通常對應的是我們的一個業務邏輯服務實現類。但是我們的服務實現類是怎麼進行消息的發送的呢?這通常是利用Spring爲我們提供的JmsTemplate類來實現的,所以配置生產者其實最核心的就是配置進行消息發送的JmsTemplate。對於消息發送者而言,它在發送消息的時候要知道自己該往哪裏發,爲此,我們在定義JmsTemplate的時候需要往裏面注入一個Spring提供的ConnectionFactory對象。
在真正利用JmsTemplate進行消息發送的時候,我們需要知道消息發送的目的地,即destination。在Jms中有一個用來表示目的地的Destination接口,它裏面沒有任何方法定義,只是用來做一個標識而已。當我們在使用JmsTemplate進行消息發送時沒有指定destination的時候將使用默認的Destination。默認Destination可以通過在定義jmsTemplate bean對象時通過屬性defaultDestination或defaultDestinationName來進行注入,defaultDestinationName對應的就是一個普通字符串。在ActiveMQ中實現了兩種類型的Destination,一個是點對點的ActiveMQQueue,另一個就是支持訂閱/發佈模式的ActiveMQTopic。在定義這兩種類型的Destination時我們都可以通過一個name屬性來進行構造,如:
- <!--這個是隊列目的地,點對點的-->
- <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>queue</value>
- </constructor-arg>
- </bean>
- <!--這個是主題目的地,一對多的-->
- <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
- <constructor-arg value="topic"/>
- </bean>
假設我們定義了一個ProducerService,裏面有一個向Destination發送純文本消息的方法sendMessage,那麼我們的代碼就大概是這個樣子:
- 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;
- @Component
- public 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方法,當接收到消息的時候會自動調用該方法。
- package com.tiantian.springintejms.listener;
- 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) {
- //這裏我們知道生產者發送的就是一個純文本消息,所以這裏可以直接進行強制轉換
- TextMessage textMsg = (TextMessage) message;
- System.out.println("接收到一個純文本消息。");
- try {
- System.out.println("消息內容是:" + textMsg.getText());
- } catch (JMSException e) {
- e.printStackTrace();
- }
- }
- }
有了MessageListener之後我們就可以在Spring的配置文件中配置一個消息監聽容器了。
- <!--這個是隊列目的地-->
- <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
- <constructor-arg>
- <value>queue</value>
- </constructor-arg>
- </bean>
- <!-- 消息監聽器 -->
- <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>
- <!-- 消息監聽容器 -->
- <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>
我們可以看到我們定義了一個名叫queue的ActiveMQQueue目的地,我們的監聽器就是監聽了發送到這個目的地的消息。
至此我們的生成者和消費者都配置完成了,這也就意味着我們的整合已經完成了。這個時候完整的Spring的配置文件應該是這樣的:
- <?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>
- <!-- 消息監聽器 -->
- <bean id="consumerMessageListener" class="com.tiantian.springintejms.listener.ConsumerMessageListener"/>
- <!-- 消息監聽容器 -->
- <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>
- </beans>
接着我們來測試一下,看看我們的整合是否真的成功了,測試代碼如下:
- 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
- private ProducerService producerService;
- @Autowired
- @Qualifier("queueDestination")
- private Destination destination;
- @Test
- public void testSend() {
- for (int i=0; i<2; i++) {
- producerService.sendMessage(destination, "你好,生產者!這是消息:" + (i+1));
- }
- }
- }
在上面的測試代碼中我們利用生產者發送了兩個消息,正常來說,消費者應該可以接收到這兩個消息。運行測試代碼後控制檯輸出如下:
看,控制檯已經進行了正確的輸出,這說明我們的整合確實是已經成功了。