jms與ActiveMQ實踐與應用
來自: http://www.iteye.com/topic/1119727
(有些詞可能用的不是很正確,在這里我把自己能意識到的詞拿出來解釋一下):
1、 跨服務器:專業術語好像叫“跨實例”。意思是,可以在多個服務器(可以是不同的服務器,如resin與tomcat)之間相互通信。與之對應的是單服務器版。
2、 消息生產者:就是專門制造消息的類。
3、 消息消費者:也叫消息接收者,它主要是實現了消息監聽的一個接口,當然,也可以難過Spring提供的一個轉換器接口指定任意一個類中的任意方法。
我們都知道,任何一個系統從整體上來看,其實質就是由無數個小的服務或事件(我們可以稱之為事務單元)有機地組合起來的。對于系統中任何一個比較復雜的功能,都是通過調用各個獨立的事務單元以實現統一的協調運作而實現的。
現在我們的問題是,如果有兩個完全獨立的服務(比如說兩個不同系統間的服務)需要相互交換數據,我們該如何實現?
好吧,我承認,我很傻很天真,我想到的第一個方法就是在需要的系統中將代碼再寫一遍,但我也知道,這絕對不現實!好吧,那我就應該好好學習學習達人們是如何去解決這樣的問題。
第一種方法,估計也是用的最多的,就是rpc模式。這種方法就是在自己的代碼中遠程調用其它程序中的代碼以達到交換數據的目的。但是這種方法很顯然地存在了一個問題:就是一定要等到獲取了數據之后才能繼續下面的操作。當然,如果一些邏輯是需要這些數據才能操作,那這就是我們需要的。
第二種方法就是Hessian,我個人覺得Hessian的實現在本質上與rpc模式的一樣,只是它采用了配置,簡化了代碼。
上面這兩個方法,基本上能解決所有的遠程調用的問題了。但是美中不足的是,如果我在A系統中有一個操作是需要讓B系統做一個響應的,但我又不需要等它響應完才做下面的操作,這該怎么辦?于是新的解決方案就需要被提出來,而SUN公司的設計師們也考慮到了,在JAVA中這就被體現為JMS(java message service)。
JMS模塊的功能只提供了接口,并沒有給予實現,實現JMS接口的消息中間件叫JMS Provider,這樣的消息中間件可以從Java里通過JMS接口進行調用。
JMS消息由兩部分構成:header和body。header包含消息的識別信息和路由信息,body包含消息的實際數據。
JMS的通用接口集合以異步方式發送或接收消息。另外, JMS采用一種寬松結合方式整合企業系統的方法,其主要的目的就是創建能夠使用跨平臺數據信息的、可移植的企業級應用程序,而把開發人力解放出來。
Java消息服務支持兩種消息模型:Point-to-Point消息(即P2P)和發布訂閱消息(Publish Subscribe messaging,簡稱Pub/Sub,也就是廣播模式)。
根據數據格式,JMS消息可分為以下五種:
BytesMessage 消息是字節流。
MapMessage 消息是一系列的命名和值的對應組合。
ObjectMessage 消息是一個流化(即繼承Serializable)的Java對象。
StreamMessage 消息是Java中的輸入輸出流。
TextMessage 消息是一個字符串,這種類型將會廣泛用于XML格式的數據。
在使用JMS時,其步驟很像使用JDBC一樣,需要的步驟為:
1、建立消息連接(也就是建立連接工廠);
2、設定消息目的地(其實與步驟1中用的類是一樣的,只是它是用來指定目的地,而步驟1中是用來指定消息服務器地址的);
3、創建jmsTemplate實例(為下一步構建消息sessin作準備);
4、創建消息生產者(其中就用到了2、3兩步的產物),它就是一個普通的類,一般是通過send方法發送消息,也可以通過MessageListenerAdapter指定發送信息的方法;
5、創建MDP(也就是消息接收者,它是一個必須實現MessageListener接口的類);
6、為每個MDP建立一個監聽容器,當有相應的消息傳來,則它會自動調用對應的MDP消費消息。
整個過程就像編寫JDBC一樣,代碼維護量很大。為此,讓Spring對其進行管理是個不錯的選擇。
Spring框架提供了一個模板機制來隱藏Java APIs的細節。開發人員可以使用JDBCTemplate和JNDITemplate類來分別訪問后臺數據庫和JEE資源(數據源,連接池)。JMS也不例外,Spring提供JMSTemplate類,因此開發人員不用為一個JMS實現去編寫樣本代碼。接下來是在開發JMS應用程序時Spring所具有一些的優勢。
1. 提供JMS抽象API,簡化了訪問目標(隊列或主題)和向指定目標發布消息時JMS的使用。
2. 開發人員不需要關心JMS不同版本(例如JMS 1.0.2與JMS 1.1)之間的差異。
3. 開發人員不必專門處理JMS異常,因為Spring為所有JMS異常提供了一個未經檢查的異常,并在JMS代碼中重新拋出
具體的詳細步驟與方法參考 spring-reference2.5.pdf 中的第十九章。
下面,我就將我在整個學習過程中實踐過的例子一一列舉出來,并將在其中遇到的問題和心得給出一定的說明,希望對大家能有所幫助。
1、首先,我們需要配置resin下的resin.conf文件,在其中(<server></server>之間)加上:
<!-- The ConnectionFactory resource defines the JMS factory for creating JMS connections -->
<resource jndi-name="jms/factory"
type="com.caucho.jms.ConnectionFactoryImpl">
</resource>
<!-- Queue configuration with Resin's database -->
<resource jndi-name="jms/queue"
type="com.caucho.jms.memory. MemoryQueue">
<init>
<queue-name>OssQueue</queue-name>
</init>
</resource>
<!-- Queue configuration with Resin's database -->
<resource jndi-name="jms/topic"
type="com.caucho.jms.memory. MemoryTopic">
<init>
<queue-name>ossTopic</queue-name>
</init>
</resource>
注:i、我現在只知道JNDI方式配置消息的連接工廠,我并不知道有沒有其它的方式,但我看了許多資料上也沒提到其它配置方式。
ii、網上很少有關于在resin中配置JMS消息工廠的資料,只有在resin的官網上才能見到。
iii、上面JNDI配置的地方需要注意的是,大家如果在網上看資料的話,可能會發現網上會比我給出的總是會多一些,也就是總是多一些<data-source>的初始化配置,如:
<resource jndi-name="jms/factory"
type="com.caucho.jms.ConnectionFactoryImpl">
<init>
<data-source>jdbc/database</data-source>
</init>
</resource>
就這樣的配置,單獨啟動resin是沒有問題的,但是如果將其按照下面的Spring配置加到系統中,就會出異常(具體的異常名稱我忘了,中文的大概意思是:數據庫對象不能轉換成JMS連接對象,還有一種情況是啟動系統時會內存溢出)。我認為這種配置可能是數據庫消息模式的配置(因為JMS有內存和數據庫兩種管理方式,我目前只學習了內存管理的方式,至于數據庫管理方式大家要是有興趣可以參考:
http://www.oracle.com/technology/books/pdfs/2352_Ch06_FINAL.pdf)
2、在web.xml文件中配置一個spring用的上下文:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/jmsconfig.xml</param-value>
</context-param>
<!-- 配置Spring容器 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
注:我是將jmsconfig.xml加載到service.xml中隨系統啟動的。
3、創建jmsconfig.xml用來裝配jms,內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="jmsConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jms/factory </value>
</property>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value> java:comp/env/jms/queue</value>
</property>
</bean>
<!-- Spring JmsTemplate config -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<bean
class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory"
ref="jmsConnectionFactory"/>
</bean>
</property>
</bean>
<!-- POJO which send Message uses Spring JmsTemplate --> <!--配置消息生產者-->
<bean id="messageProducer" class="com.focustech.jms.MessageProducer">
<property name="template" ref="jmsTemplate"/>
<property name="destination" ref="destination"/>
</bean>
<!-- Message Driven POJO (MDP) -->
<bean id="messageListener" class=" com.focustech.jms.MessageConsumer"/>
<!-- listener container,MDP無需實現接口 -->
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsConnectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
</beans>
其中:
1) jmsConnectionFactory和destination都是使用自定義的,而且你會發現,這兩個對象的加載類其實是一樣的,都是JndiObjectFactoryBean,這是從JNDI讀取連接的意思。
3) MessageProducer是消息發送方。
4) MessageConsumer實現了一個MessageListener,監聽是否收到消息。
4、發送和接收消息的class如下(主要代碼):
MessageProducer.java
public class MessageProducer {
private JmsTemplate template;
private Destination destination;
public void send(final String message) {
template.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
Message m = session.createTextMessage(message);
return m;
}
});
}
public void setDestination(Destination destination) {
this.destination = destination;
}
public void setTemplate(JmsTemplate template) {
this.template = template;
}
}
MessageConsumer.java
public class MessageConsumer implements MessageListener {
public void onMessage(Message message) {
try
{
System.out.println(((TextMessage) message).getText());
}
catch (JMSException e)
{
}
}
}
注:在上面的實例類中,由于在發送方發送的是文本消息(TextMessage),所以在上面的接收者代碼中我直接將其轉換成TextMessage就行了。如果是在真正的環境下,應該首先判斷一下對方發送的是什么類型,然后才轉換成對應的消息。
5、測試消息
為了測試的方便,可以在webroot下新建一個test.jsp,然后將下面的代碼放到JSP的代碼中,然后在網頁地址欄中輸入鏈接(如:http://oss.vemic.com/test.jsp 注:oss.vemic.com是本地服務器鏈接)就可以看到發送的消息了。
<%
try {
ServletContext servletContext = this.getServletContext();
WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext);
EN-U