Spring+ActiveMQ整合測試

yingxian_Fei發表於2017-05-07

本例用於展示Spring+ActiveMQ的簡單使用,例子實現瞭如下幾個功能:

   (1)、基於java配置ActiveMQ;

   (2)、使用ActiveMQ傳送queue訊息;

   (3)、使用ActiveMQ傳送topic訊息;

   (4)、消費ActiveMQ的queue訊息;

   (5)、消費ActiveMQ的topic訊息;

本文只是實現Spring+ActiveMQ的簡單整合使用,對於Spring+ActiveMQ講解比較詳細的部落格可以參考如下文章:

http://elim.iteye.com/blog/1893038

http://blog.csdn.net/lifetragedy/article/details/51836557

1、ActiveMQ安裝

首先從如下地址下載ActiveMQ的安裝檔案:

http://activemq.apache.org/download.html

下載對應的版本後解壓壓縮檔案(本文使用windows 64位的開發環境)。進入對應的解壓目錄(如本文中的路徑為apache-activemq-5.9.0\bin\win64)下執行如下指令碼啟動ActiveMQ:

activemq.bat
啟動成功後可以使用如下連結來訪問ActiveMQ的控制後臺,使用預設密碼登入即可(預設密碼在解壓目錄的conf目錄下的jetty-realm.properties檔案中可以檢視到,如本文的apache-activemq-5.9.0\conf\jetty-realm.properties檔案)。

http://localhost:8161/admin

注意:5.14版本的mq需要用jdk1.7以上,否則會報錯起不來。報錯資訊如下:

 Unsupported major.minor version 51.0

2、依賴jar包

主要需要匯入如下兩個jar包。

		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-core</artifactId>
			<version>5.7.0</version>
		</dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

3、java配置原始碼

本文中的配置使用java類+註解的方式配置,相關的配置原始碼如下:

package cn.hi_fei.ActiveMQ.configuration;

import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageListener;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.DefaultMessageListenerContainer;

import cn.hi_fei.ActiveMQ.listener.ConsumerMessageListener;

@Configuration
@ComponentScan(value = { "cn.hi_fei.ActiveMQ.service.impl" })
public class RootConfiguration {

	@Bean("targetConnectionFactory")
	public ConnectionFactory getTargetConnectionFactory() {
		ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
		factory.setBrokerURL("tcp://localhost:61616");
		return factory;
	}

	/**
	 * Get spring MQ connection factory.
	 * 
	 * @param mqFactory
	 * @return
	 */
	@Bean("springConnectionFactory")
	public ConnectionFactory getSpringConnectionFactory(
			ConnectionFactory targetConnectionFactory) {
		SingleConnectionFactory factory = new SingleConnectionFactory();
		factory.setTargetConnectionFactory(targetConnectionFactory);
		return factory;
	}

	/**
	 * Get JMS template by spring connection factory.
	 * @param springConnectionFactory
	 * @return
	 */
	@Bean
	public JmsTemplate getJmsTemplate(ConnectionFactory springConnectionFactory) {
		JmsTemplate template = new JmsTemplate();
		template.setConnectionFactory(springConnectionFactory);
		return template;
	}

	/**
	 * Create a queue destination by active MQ.
	 * @return
	 */
	@Bean("queueDestination")
	public Destination getQueueDestination() {
		ActiveMQQueue queue = new ActiveMQQueue("queue");
		return queue;
	}
	
	/**
	 * Create a queue destination by active MQ.
	 * @return
	 */
	@Bean("topicDestination")
	public Destination getTopicDestination() {
		ActiveMQTopic topic = new ActiveMQTopic("topic");
		return topic;
	}	

	@Bean
	@Profile("customer")
	public MessageListener getConsumerMessageListener() {
		ConsumerMessageListener listener = new ConsumerMessageListener();
		return listener;
	}

	@Bean
	@Profile("customer")
	public DefaultMessageListenerContainer getQueueMessageListenerContainer(
			ConnectionFactory springConnectionFactory,
			Destination queueDestination,
			MessageListener consumerMessageListener) {
		DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
		container.setConnectionFactory(springConnectionFactory);
		container.setDestination(queueDestination);
		container.setMessageListener(consumerMessageListener);
		return container;
	}
	
	@Bean
	@Profile("customer")
	public DefaultMessageListenerContainer getTopicMessageListenerContainer(
			ConnectionFactory springConnectionFactory,
			Destination topicDestination,
			MessageListener consumerMessageListener) {
		DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
		container.setConnectionFactory(springConnectionFactory);
		container.setDestination(topicDestination);
		container.setMessageListener(consumerMessageListener);
		return container;
	}	
}

4、訊息生產者實現

訊息生產者用於傳送訊息到目的地。本文中在介面IProducerService中進行定義,定義的原始碼如下:

package cn.hi_fei.ActiveMQ.service;

import javax.jms.Destination;

public interface IProducerService {

	/**
	 * 傳送普通的純文字訊息
	 * @param destination
	 * @param message
	 */
	public void sendMessage(final Destination destination,final String message);
}

介面的實現原始碼如下:

package cn.hi_fei.ActiveMQ.service.impl;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;

import cn.hi_fei.ActiveMQ.service.IProducerService;

@Component
public class ProducerServiceImpl implements IProducerService{
	private Logger logger = Logger.getLogger(ProducerServiceImpl.class);
	
	@Autowired
	private JmsTemplate jmsTemplate; 

	public void sendMessage(Destination destination, final String message) {
		logger.info("---------------send message by producer:" + message);  
        jmsTemplate.send(destination, new MessageCreator() {  
            public Message createMessage(Session session) throws JMSException {  
                return session.createTextMessage(message);  
            }  
        });  		
	}
}

5、訊息監聽器

訊息監聽器用於監聽訊息,通常用於監聽、讀取訊息並通知消費者進行消費。本文中的訊息監聽器實現原始碼如下:

package cn.hi_fei.ActiveMQ.listener;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import org.apache.log4j.Logger;

public class ConsumerMessageListener implements MessageListener{
	
	private Logger logger = Logger.getLogger(ConsumerMessageListener.class);

	public void onMessage(Message arg0) {
		if(arg0 instanceof TextMessage)  {
			TextMessage tMsg = (TextMessage)arg0;
			try {
				logger.info(tMsg.getText());
			} catch (JMSException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}		
	}

}

6、測試類

6.1、基類

老習慣,首先建立一個測試基類(BaseTester類)用於統一載入配置,具體的測試實現在其子類中。基類的原始碼如下:

package cn.hi_fei.ActiveMQ;

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.hi_fei.ActiveMQ.configuration.RootConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={RootConfiguration.class})
public class BaseTester {

}

6.2、生產者測試類

生產者測試類繼承與測試基類,實現傳送訊息到queue和topic。在測試類中僅啟用了“producer”的profile,所以不會建立訊息監聽容器。原始碼如下:

package cn.hi_fei.ActiveMQ.impl;

import javax.jms.Destination;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ActiveProfiles;

import cn.hi_fei.ActiveMQ.BaseTester;
import cn.hi_fei.ActiveMQ.service.IProducerService;

@ActiveProfiles("producer")
public class ProducerTester extends BaseTester{
	private Logger logger = Logger.getLogger(ProducerTester.class);
	
	@Autowired
	private IProducerService mProducer;
	
	@Autowired
	@Qualifier("queueDestination")
	private Destination mQueueDestination;
	
	@Autowired
	@Qualifier("topicDestination")
	private Destination mTopicDestination;
	
	@Test
	public void sendMessage() {
		logger.info("Send message to queue...");
		mProducer.sendMessage(mQueueDestination, "123123123");
		logger.info("Send message to queue...");
		mProducer.sendMessage(mTopicDestination, "456789");		
		logger.info("Active producer end...");
	}

}
執行此測試用例,結果如下:

圖1、單獨執行生產者傳送訊息到queue和topic

此時開啟ActiveMQ的控制檯可以在queue和topic中即看到傳送的訊息個數,但是本例中還沒有相關消費者對訊息進行消費。

6.3、消費者測試類

消費者測試類也繼承於測試基類,但是啟用了“customer”的profile,此時訊息監聽器的容器建立,應用進行訊息的消費。相關原始碼如下:

package cn.hi_fei.ActiveMQ.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.test.context.ActiveProfiles;

import cn.hi_fei.ActiveMQ.BaseTester;

@ActiveProfiles("customer")
public class CustomerTester extends BaseTester{
	private Logger logger = Logger.getLogger(CustomerTester.class);
	
	@Test
	public void waitToRead()  {               	
		try {
			logger.info("Wait to customer queue message.");
			InputStreamReader is_reader = new InputStreamReader(System.in);   
			new BufferedReader(is_reader).readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

原始碼比較粗糙。啟動後就一直執行直到使用者按下任意按鍵退出。執行多個消費者測試類,然後再執行生產者測試類,可用於檢視queue和topic的消費區別。如下為執行兩個測試類例項,然後生產者測試類傳送訊息後消費者消費訊息的結果:

圖2、第1個消費者的訊息消費結果

圖3、第2個消費者的消費結果

說明:

  (1)、圖2中的第一個“123123123”是之前單獨執行生產者時傳送的訊息,被本次執行的消費者給消費了;

  (2)、圖2中的第二個“123123123”是兩個消費者執行起來後執行生產者傳送的訊息,對比圖3的消費結果可以看出來queue中的訊息只會被一個消費者消費;

  (3)、圖2和圖3中的訊息“456789”是topic訊息,對比可以看出兩個消費者都收到了該主題訊息。

7、常見問題

(1)、ActiveMQ無法啟動

在使用ActiveMQ5.14的時候出現ActiveMQ無法啟動的問題,檢視啟動日誌(在安裝目錄的data目錄下),發現如下錯誤資訊:

WrapperSimpleApp: Unable to locate the class org.apache.activemq.console.Main: java.lang.UnsupportedClassVersionError: org/apache/activemq/console/Main : Unsupported major.minor version 51.0

這個問題是由於執行時的jdk與編譯class的jdk不一致造成的,解決辦法是更換jdk,具體使用什麼版本的jdk與activeMQ的版本有關係。我們可以在ActiveMQ的啟動指令碼中指定需要單獨使用的jdk版本。具體修改可以參考http://blog.csdn.net/smilefyx/article/details/49511975中指定tomcat的方法做修改。

(2)、執行時提示org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/StaticLoggerBinder錯誤

在執行程式碼時出現如下類似的錯誤從而導致bean建立失敗,程式碼無法執行:

org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/StaticLoggerBinder
該問題是由於缺少log庫或者log庫的版本不對,導致無法找到某些方法導致。本文中的解決辦法是新增如下依賴:

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.25</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.25</version>
		</dependency>

8、原始碼下載:

原始碼可從如下地址進行下載:

http://download.csdn.net/detail/yxtouch/9835989

相關文章