Spring 配置 ActiveMQ

xz43發表於2016-05-29

點選(此處)摺疊或開啟

  1. <!-- ////////////////////////////////////////// -->
  2. <!-- For JMS (ActiveMQ) Begin -->
  3. <!-- ////////////////////////////////////////// -->
  4. <bean id="broker"
  5. class="org.apache.activemq.xbean.BrokerFactoryBean">
  6. <property name="config" value="classpath:activemq.xml" />
  7. <property name="start" value="true" />
  8. </bean>

  9. <bean id="jmsConnectionFactory"
  10. class="org.apache.activemq.ActiveMQConnectionFactory"
  11. depends-on="broker">
  12. <property name="brokerURL" value="vm://billing" />
  13. </bean>

  14. <!--<amq:connectionFactory id="jmsConnectionFactory" brokerURL="vm://localhost"/>-->

  15. <bean id="jmsFactoryPool"
  16. class="org.apache.activemq.pool.PooledConnectionFactory">
  17. <property name="connectionFactory" ref="jmsConnectionFactory" />
  18. </bean>

  19. <bean id="jmsTransactionManager"
  20. class="org.springframework.jms.connection.JmsTransactionManager">
  21. <property name="connectionFactory" ref="jmsConnectionFactory" />
  22. </bean>

  23. <bean id="messageConverter"
  24. class="org.springframework.jms.support.converter.SimpleMessageConverter" />

  25. <bean id="jmsTemplate"
  26. class="org.springframework.jms.core.JmsTemplate">
  27. <property name="connectionFactory" ref="jmsFactoryPool" />
  28. <property name="messageConverter" ref="messageConverter" />
  29. </bean>

  30. <!-- ActiveMQ destinations to use -->
  31. <amq:queue name="mBSettlementQueue"

  32. physicalName="pp.queue.MBSettlementQueue" />

  33. <!-- POJO which send success message by JmsTemplate and queue -->
  34. <bean id="settlementMsgProducer"
  35. class="com.pp.billing.settlement.SettlementMsgProducer">
  36. <property name="jmsTemplate" ref="jmsTemplate" />
  37. <property name="destination" ref="mBSettlementQueue" />
  38. </bean>    

  39. <bean id="settlementMsgListener"
  40. class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
  41. <constructor-arg>
  42. <bean
  43. class="com.PP.billing.settlement.SettlementMsgListener">
  44. </bean>
  45. </constructor-arg>
  46. <!-- may be other method -->
  47. <property name="defaultListenerMethod" value="onMessage" />
  48. <!-- custom MessageConverter define -->
  49. <property name="messageConverter" ref="messageConverter" />
  50. </bean>    

  51. <bean id="listenerContainer"
  52. class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  53. <!-- property name="transactionManager" ref="jmsTransactionManager" / -->

  54.   <property name="connectionFactory" ref="jmsConnectionFactory" />
  55. <property name="destination" ref="mBSettlementQueue" />
  56. <property name="messageListener" ref="settlementMsgListener" />
  57. <property name="concurrentConsumers" value="2" />
  58. <property name="recoveryInterval" value="3000" />
  59. <property name="receiveTimeout" value="10000" />
  60. </bean>

點選(此處)摺疊或開啟

  1. public class SoapGWProducer {
  2.     /**
  3.      *define log object to print log record
  4.      */
  5.     private static Log log = LogFactory.getLog(SoapGWProducer.class);

  6.     /**
  7.      *define jmsTemplate
  8.      */
  9.     private JmsTemplate jmsTemplate;

  10.     /**
  11.      *define destination
  12.      */
  13.     private Queue destination;

  14.     /**
  15.      * @return Returns jmsTemplate.
  16.      */
  17.     public JmsTemplate getJmsTemplate() {
  18.         return jmsTemplate;
  19.     }

  20.     /**
  21.      * @param jmsTemplate The jmsTemplate to set.
  22.      */
  23.     public void setJmsTemplate(JmsTemplate jmsTemplate) {
  24.         this.jmsTemplate = jmsTemplate;
  25.     }


  26.     /**
  27.      * @return destination
  28.      */
  29.     public Queue getDestination() {
  30.         return destination;
  31.     }

  32.     /**
  33.      * @param destination destination
  34.      */
  35.     public void setDestination(Queue destination) {
  36.         this.destination = destination;
  37.     }

  38.     /**
  39.      * send message to queue
  40.      * @param message message
  41.      */
  42.     public void send(SoapGWMessage message) {
  43.         try {
  44.             jmsTemplate.convertAndSend(this.destination, message);
  45.         } catch (Exception e) {
  46.             log.error("send SoapGWMessage to SoapGWQueue error:"
  47.                     + e.getMessage(), e);
  48.         }
  49.     }
  50. }
  51. public class SoapGWListener {

  52.     /**
  53.      *define log object to print the log content
  54.      */
  55.     private static Log log = LogFactory.getLog(SoapGWListener.class);

  56.     /**
  57.      *define start message content
  58.      */
  59.     public static final String START_MESSAGE = "Start";

  60.     /**
  61.      * listerner message and do mblox SoapGWMessage
  62.      * @param message message
  63.      */
  64.     public void onMessage(SoapGWMessage message) {
  65.         if (log.isInfoEnabled()) {
  66.             log.info("start SoapGWListener onMessage(),parameter is:" + message);
  67.         }
  68.         //get message informatio
  69.         String phoneNumber = message.getPhoneNumber();
  70.     }


點選(此處)摺疊或開啟

  1. activemq.xml

  2. <?xml version="1.0" encoding="ISO-8859-1"?>

  3. <beans>
  4.     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>

  5.     <broker xmlns="" brokerName="localhost">
  6.     <persistenceAdapter>
  7.         <jdbcPersistenceAdapter dataSource="#mssql-ds" useDatabaseLock="false">
  8.             <statements>
  9.                 <statements binaryDataType ="binary(1024)"/>
  10.             </statements>
  11.          </jdbcPersistenceAdapter>
  12.     </persistenceAdapter>

  13.       <!-- Use the following to configure how ActiveMQ is exposed in JMX -->
  14.       <managementContext>
  15.          <managementContext connectorPort="1099" jmxDomainName="org.apache.activemq"/>
  16.       </managementContext>
  17.     </broker>

  18.     <bean id="mssql-ds" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  19.       <property name="driverClass" value="$DB_DRIVERNAME"/>
  20.       <property name="jdbcUrl" value="$DB_QUEUE_URL"/>
  21.       <property name="user" value="$DB_QUEUE_USER"/>
  22.       <property name="password" value="$DB_QUEUE_PASSWD"/>
  23.       <property name="initialPoolSize" value="50"/>
  24.       <property name="minPoolSize" value="20"/>
  25.       <property name="maxPoolSize" value="1000"/>
  26.       <property name="acquireIncrement" value="30"/>
  27.       <property name="maxIdleTime" value="100"/>
  28.       <property name="maxStatements" value="10"/>
  29.     </bean>
  30. </beans>

上文可見,JMS Native API使用起來不是特別方便。好在Spring提供了很好的JMS支援。

(一)配置ConnectionFactory 
如果使用連線池的話,不要忘記activemq-pool-5.6.0.jar 

點選(此處)摺疊或開啟

  1. <bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
  2.  <property name="connectionFactory" ref="innerConnectionFactory" />
  3.  </bean>
  4. <bean id="innerConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  5.  <property name="brokerURL" value="tcp://localhost:61616" />
  6. </bean>

(二)配置一個Destination作為預設投遞目標 

點選(此處)摺疊或開啟

  1. <bean id="defaultDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg index="0" value="ztgame.amt.default.queue" />
  2. </bean>
(三)核心JmsTemplate 

點選(此處)摺疊或開啟

  1. <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> 
  2. <property name="connectionFactory" ref="connectionFactory" /> 
  3. <property name="defaultDestination" ref="defaultDestination" />
  4. </bean>
(四)JMS事務

點選(此處)摺疊或開啟

  1. <bean id="jmsTransactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
  2.  <property name="connectionFactory" ref="connectionFactory" />
  3. </bean>
  4. <tx:annotation-driven transaction-manager="jmsTransactionManager" />
(五)編寫訊息驅動Bean,兩個介面實現一個即可 

點選(此處)摺疊或開啟

  1. javax.jms.MessageListener
  2. org.springframework.jms.listener.SessionAwareMessageListener

  3. package com.ztgame.amt.jms.mdp;
  4. import javax.jms.JMSException;
  5. import javax.jms.Session;
  6. import javax.jms.TextMessage;
  7. import org.springframework.jms.listener.SessionAwareMessageListener;
  8. import org.springframework.stereotype.Component; @Component("defaultMessageHandler")
  9. public class DefaultMessageHandler implements SessionAwareMessageListener<TextMessage> {

  10. public void onMessage(TextMessage message, Session session) throws JMSException {
  11.  String text = message.getText();
  12.  if (text.length() == 0) {
  13.  System.out.println("<empty>");
  14.  } else {
  15.  System.out.println(text);
  16.  }
  17.  }
  18.  }
(六)配置訊息驅動Bean

點選(此處)摺疊或開啟

  1. <!-- Message Driven POJOs -->
  2. <jms:listener-container connection-factory="connectionFactory">
  3.  <jms:listener destination="ztgame.amt.default.queue" ref="defaultMessageHandler" />
  4. </jms:listener-container>
(七)編寫測試程式碼

點選(此處)摺疊或開啟

  1. package junit;
  2. import java.util.UUID;
  3. import javax.annotation.Resource;
  4. import javax.jms.JMSException;
  5. import javax.jms.Message;
  6. import javax.jms.Session;
  7. import org.junit.Test;
  8. import org.springframework.jms.core.JmsTemplate;
  9. import org.springframework.jms.core.MessageCreator;
  10. import org.springframework.test.context.ContextConfiguration;
  11. import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

  12. @ContextConfiguration({ "classpath:spring-beans.xml", "classpath:spring-jms.xml" })

  13. public class TestSomeService extends AbstractJUnit4SpringContextTests {
  14.  @Resource private JmsTemplate jmsTempalte;

  15.  @Test
  16.  public void test() {
  17.  jmsTempalte.send(
  18.    new MessageCreator() {
  19.     public Message createMessage(Session session) throws JMSException {
  20.      return session.createTextMessage(UUID.randomUUID().toString());
  21.     } });
  22.  }
  23.  }


前言
這是我自己從不知道JMS為何物到學習如何使用第三方工具實現跨伺服器的知識總結,在整個過程中可能考慮不全。另外,如果想盡快使用JMS,建議直接看例項那一節就可以了。有問題多交流。
詞語解釋
(有些詞可能用的不是很正確,在這裡我把自己能意識到的詞拿出來解釋一下):
1、  跨伺服器:專業術語好像叫“跨例項”。意思是,可以在多個伺服器(可以是不同的伺服器,如resin與tomcat)之間相互通訊。與之對應的是單伺服器版。
2、  訊息生產者:就是專門製造訊息的類。
3、  訊息消費者:也叫訊息接收者,它主要是實現了訊息監聽的一個介面,當然,也可以難過Spring提供的一個轉換器介面指定任意一個類中的任意方法。

我們都知道,任何一個系統從整體上來看,其實質就是由無數個小的服務或事件(我們可以稱之為事務單元)有機地組合起來的。對於系統中任何一個比較複雜的功能,都是透過呼叫各個獨立的事務單元以實現統一的協調運作而實現的。
現在我們的問題是,如果有兩個完全獨立的服務(比如說兩個不同系統間的服務)需要相互交換資料,我們該如何實現?
好吧,我承認,我很傻很天真,我想到的第一個方法就是在需要的系統中將程式碼再寫一遍,但我也知道,這絕對不現實!好吧,那我就應該好好學習學習達人們是如何去解決這樣的問題。
第一種方法,估計也是用的最多的,就是rpc模式。這種方法就是在自己的程式碼中遠端呼叫其它程式中的程式碼以達到交換資料的目的。但是這種方法很顯然地存在了一個問題:就是一定要等到獲取了資料之後才能繼續下面的操作。當然,如果一些邏輯是需要這些資料才能操作,那這就是我們需要的。
第二種方法就是Hessian,我個人覺得Hessian的實現在本質上與rpc模式的一樣,只是它採用了配置,簡化了程式碼。
上面這兩個方法,基本上能解決所有的遠端呼叫的問題了。但是美中不足的是,如果我在A系統中有一個操作是需要讓B系統做一個響應的,但我又不需要等它響應完才做下面的操作,這該怎麼辦?於是新的解決方案就需要被提出來,而SUN公司的設計師們也考慮到了,在JAVA中這就被體現為JMS(java message service)。
一、認識JMS
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
在使用JMS時,其步驟很像使用JDBC一樣,需要的步驟為:
1、建立訊息連線(也就是建立連線工廠);
2、設定訊息目的地(其實與步驟1中用的類是一樣的,只是它是用來指定目的地,而步驟1中是用來指定訊息伺服器地址的);
3、建立jmsTemplate例項(為下一步構建訊息sessin作準備);
4、建立訊息生產者(其中就用到了2、3兩步的產物),它就是一個普通的類,一般是透過send方法傳送訊息,也可以透過MessageListenerAdapter指定傳送資訊的方法;
5、建立MDP(也就是訊息接收者,它是一個必須實現MessageListener介面的類);
6、為每個MDP建立一個監聽容器,當有相應的訊息傳來,則它會自動呼叫對應的MDP消費訊息。
整個過程就像編寫JDBC一樣,程式碼維護量很大。為此,讓Spring對其進行管理是個不錯的選擇。
三、Spring整合JMS
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有記憶體和資料庫兩種管理方式,我目前只學習了記憶體管理的方式,至於資料庫管理方式大家要是有興趣可以參考:

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"
    "">  
 
<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的程式碼中,然後在網頁位址列中輸入連結(如: 注:oss.vemic.com是本地伺服器連結)就可以看到傳送的訊息了。
<%
try {
     ServletContext servletContext = this.getServletContext();
     WebApplicationContext wac = WebApplicationContextUtils
      .getRequiredWebApplicationContext(servletContext);
     MessageProducer mp = (MessageProducer) wac.getBean("messageProducer");
           mp.send("JMS TEST!!");
      } catch (JmsException e) {
  }
%>
(二)、配置跨伺服器(即兩個或多個resin之間)版訊息機制
上面介紹的是單伺服器的訊息模式配置,使用訊息模式,是因為我們需要在兩個或多個伺服器之間進行訊息的傳遞,而不是單個伺服器內容的訊息傳遞。看過很多資料才發現,幾乎所有的伺服器都不支援跨伺服器的訊息模式,就算有(如JBoss),那也是因為它們本身整合了第三方的工具而實現的。而在第三方軟體裡面,我最終選擇了apache activeMQ。
apache activeMQ的簡介可以去其官網檢視:
或參考:http://www.blogjava.net/cctvx1/archive/2007/02/07/98457.html
I、ActiveMQ的安裝與配置
在其官網上下載最新的對應系統的版本。一般來說,下載完解壓之後就可能透過執行:apache-activemq-5.2.0\bin\ activemq.bat就可以成功啟動。具體詳細的資訊參考:
http://andyao.javaeye.com/blog/153171,或者也可參考其官網。
II、整合Spring的JMS訊息傳送
在真正操作之前,為了不至於糊塗,我們應該忘掉前面所說的所有配置(當然,JMS的基礎知識我們還是應該記住的,因為所有的JMS操作都是基於此的。還有那兩個生產訊息與消費訊息的類與測試頁面我們也要保留,因為我們下面還需要它們),好吧,現在將所有的配置迴歸到開始的狀態吧(如:resin.conf, JMS在Spring中的配置等等都回到原始狀態吧)。
先說一下我執行時所需的環境吧:JDK1.5.0_12和JDK1.6.0_05都可以;resin-3.0.25(其它版本沒有試過);配置ActiveMQ所需的JAR包有:
activemq-core-5.2.0.jar、activemq-web-5.2.0.jar、geronimo-j2ee-management_1.0_spec-1.0.jar、geronimo-jms_1.1_spec-1.1.1.jar、geronimo-jta_1.0.1B_spec-1.0.1.jar、xbean-spring-3.4.jar。
好了,一切準備就緒了。那就讓ActiveMQ先在系統中執行吧(也就是先單伺服器執行,先易後難嘛)。為了讓它能夠執行起來,我們需要做以下的準備工作:
1、 使用ActiveMQ的JMS在Spring中的配置
其實這裡的許多配置和上面說的單伺服器的配置是差不多的,只是這裡不再需要配置resin,web.xml的配置與上面的一模一樣(當然,我還是按照我的方式配置在了service.xml中),好了,現在不同的配置是jmsconfig.xml:
<beans xmlns=""
       xmlns:amq=""
       xmlns:xsi=""
       xsi:schemaLocation=" /spring-beans.xsd
  http://people.apache.org/repository/org.apache.activemq/xsds/activemq-core-4.1-incubator-SNAPSHOT.xsd">
       <!-- 配置ActiveMQ服務 -->
       <amq:broker useJmx="false" persistent="false">
              <amq:transportConnectors>
<!-- ActiveMQ目前支援的transport有:VM Transport、TCP Transport、SSL Transport、Peer Transport、UDP Transport、Multicast Transport、HTTP and HTTPS Transport、Failover Transport、Fanout Transport、Discovery Transport、ZeroConf Transport等-->
                     <amq:transportConnector uri="tcp://test.vemic.com:61616" />
              </amq:transportConnectors>
       </amq:broker>
       <!-- 配置JMS連線工廠(注:brokerURL是關鍵,它應該是上面的amq:transportConnectors裡面的值之一對應,因為這裡指定連線的物件) -->
       <amq:connectionFactory id="jmsConnectionFactory"
              brokerURL="tcp://test.vemic.com:61616" />
       <!-- 訊息傳送的目的地(注:”amq:queue”是用於指定是傳送topic還是queue) -->
       <amq:queue name="destination" physicalName="ossQueue" />
       <!-- 建立JMS的Session生成類,也就是jmsTemplate -->
       <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>
       <!-- 訊息生產者(透過指定目的地, 就可以同時指定其傳送的訊息模式是topic還是queue) -->
       <bean id="messageProducer"
              class="com.focustech.jms.MessageProducer">
              <property name="template" ref="jmsTemplate" />
              <property name="destination" ref="destination" />
       </bean>
       <!-- 訊息接收類(這個類需要繼承javax.jms.MessageListener,當然也可以透過MessageListenerAdapter指定訊息轉換器來實現使用者自定義的訊息收發) -->
       <bean id="messageListener"
              class=" com.focustech.jms.MessageConsumer">
       </bean>
       <!-- 訊息監聽容器,其各屬性的意義為:
              connectionFactory:指定所監聽的物件,在這裡就是監聽連線到tcp://test.vemic.com:61616上面的ActiveMQ;
              destination:監聽的訊息模式;
              messageListener:接收者
              ) -->
       <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>
注:test.vemic.com是我本機的URL,和localhost一樣。
2、 訊息測試
採用上面單機測試的訊息就可以了。最終執行的結果為:
JMS TEST!!
注:
注意到了沒有?上面的配置從jmsTemplate開始往下就和前面介紹過的單伺服器的配置一樣了。看到這裡,我相信大家對JMS的工作過程應該很清楚了。我個人認為我們可以簡單的這樣理解其工作過程:
生產者
 
消費者
 
JMS connectionFactory
產生訊息併發往指定的目的地
接收訊息並給出已消費確認資訊,JMS connectionFactory收到確認資訊後就將對應的資訊從自己的管理庫中刪除
從上面的圖很清楚地看出,要想實現跨伺服器的JMS訊息機制,JMS connectionFactory是關鍵的地方,簡單地說:connectionFactory就決定了JMS的作用範圍。如果connectionFactory是受制於系統(也就是說,當系統停掉之後connectionFactory也就跟著銷燬),那麼它就不能實現跨伺服器功能。要想實現跨服務功能,connectionFactory就必須獨立於系統或伺服器。由此可見,結合前面的知識,我們就可以知道,能夠實現跨伺服器的JMS訊息機制其實有兩種方式:JDBC方式和採用第三方工具。前面我也說過,我選擇了後者(而且我也一直這麼做了)。
3、  實現多伺服器的JMS共享,即實現JMS跨伺服器功能。
ActiveMQ的單伺服器版我們已經成功搭建並能成功執行了。現在讓我們實現JMS跨伺服器功能吧。等等,我們先準備另一個伺服器環境。為了明顯的區別兩個服務,我將上面所有的環境重新弄了一份(一個新的MyEclipse;一個新的web工程,當然web工程裡面的環境與上面的一樣;一個新的resin;一個新的resin埠8081,上面的resin埠是80),我稱之為client。
接下來,我們來配置client中的JMS。在所有的系統配置中只有一個配置檔案與上面的有區別,那就是jmsconfig.xml:
<beans xmlns=""
       xmlns:amq=""
       xmlns:xsi=""
       xsi:schemaLocation=" /spring-beans.xsd
  http://people.apache.org/repository/org.apache.activemq/xsds/activemq-core-4.1-incubator-SNAPSHOT.xsd">
       <!-- 配置JMS連線工廠(注:brokerURL是關鍵,它應該是上面的amq:transportConnectors裡面的值之一對應,因為這裡指定連線的物件) -->
       <amq:connectionFactory id="jmsConnectionFactory"
              brokerURL="tcp://test.vemic.com:61616" />
       <!-- 訊息傳送的目的地(注:” amq:queue”是用於指定是傳送topic還是queue,對應上面配置中的amq:destinations) -->
       <amq:queue name="destination" physicalName="ossQueue" />
       <!-- 建立JMS的Session生成類 -->
       <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>
       <!-- 訊息生產者(透過指定目的地, 就可以同時指定其傳送的訊息模式是topic還是queue) -->
       <bean id="messageProducer"
              class="com.focustech.jms.MessageProducer">
              <property name="template" ref="jmsTemplate" />
              <property name="destination" ref="destination" />
       </bean>
       <!-- 訊息接收類(這個類需要繼承javax.jms.MessageListener,當然也可以透過MessageListenerAdapter指定訊息轉換器來實現使用者自定義的訊息收發) -->
       <bean id="messageListener"
              class="com.focustech.jms.MessageConsumer">
       </bean>
       <!-- 訊息監聽容器,其各屬性的意義為:
              connectionFactory:指定所監聽的物件,在這裡就是監聽連線到tcp://test.vemic.com:61616上面的ActiveMQ;
              destination:監聽的訊息模式;
              messageListener:接收者
              ) -->
       <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>
注意了!這個配置與上面的ActiveMQ的配置只有一個地方不一樣,也就是:這個配置中沒有配置ActiveMQ的相關資訊。為什麼?結合上面所述的JMS簡單工作方式,我們應該不難得到答案:因為ActiveMQ要實現跨伺服器就必須獨立執行,所以我們只需要啟動一個就夠了。
注:
其實在這個簡單的跨伺服器的例子中,其中一方只需要配置訊息生產者,而另一方只需要配置訊息的消費者就可以測試透過了,而且測試效果會很明顯:在訊息生產者的那個伺服器上執行測試程式,在訊息接收的伺服器上就會有相應的響應!在這裡我之所以這樣做,是讓大家在測試時發現一個奇怪的現象:當在一端執行測試程式,第一個訊息會被當前執行測試程式的服務消費掉,而接下來的訊息又被另一個伺服器消費掉,如此迴圈(我沒有測試過三個及以上的伺服器執行情況,我想可能是訊息一個個的均攤下去的吧)。之所以要讓大家看到這個現象,是為了讓大家有個疑問,容易接受後面高階應用中關於JMS的兩種訊息機制:Point-to-Point訊息(即P2P)和釋出訂閱訊息(Publish Subscribe messaging,簡稱Pub/Sub,也就是廣播模式)的使用方法。
III、高階應用
從這裡開始,我們將進入JMS訊息的一些特殊用法,或者叫高階應用。在介紹這些應用的時候,我們會用到上面已經佈署好的跨伺服器的應用來作例子。為了區別兩個伺服器,我們把配置有ActiveMQ的叫server,另一個還是叫client。
1、使用指定的訊息,即訊息的P2P與Pub/Sub的應用
上節內容的最後給大家留下了一個有趣的現象。在這裡,我們將針對這個現象進行詳細的解析。
我們前面也知道了,訊息模式有兩種,但怎麼使用卻一直沒提過。但是如果仔細的看過官網的資料也許已經知道一些了。在這裡,我將用例項的方式給大家展現一下它的具體使用。(參考:http://andyao.javaeye.com/blog/234101)
首先,我們來看Queue訊息的使用例項。上面的跨伺服器的例項其實就是queue例項的應用,但問題是:如何指定唯一的接收者呢?也就是不能出現上面提到的那個奇怪的迴圈現象呢?
其實這個現象也並不難回答,首先讓我們來仔細看一下queue訊息的目的地配置:
<amq:queue name="destination" physicalName="ossQueue" />
對於上面的配置,我們可以一一解讀其中各引數的含義就知道奧妙所在了:
amq:queue:表示這是配置是queue訊息;
name:指定的訊息傳送與接收的目的地的名稱;
physicalName:指定訊息佇列的物理名稱,在ActiveMQ中它就是一個訊息叢集的表示形式。
根據上面配置的含義,我們不難發現,其實奧妙就在physicalName這個屬性中體現的。具體來說,對於queue訊息而言,只要傳送方與接收方都使用同一個physicalName,這就是點對點指定了。例如:將上面的例子中client中的:
<amq:queue name="destination" physicalName="ossQueue" />
改成:
<amq:queue name="destination" physicalName="ossQueue1" />
這樣的話,我們上面說的“奇怪的現象”就不會存在了。因為server中訊息是傳送到ossQueue這個訊息佇列裡的,而client中訊息目的地是指向ossQueue1的,當然就收不到server裡面ossQueue中的訊息了。
我們再來看一下如何使用Topic訊息。其實很簡單,只要將第II節配置中的
<amq:queue name="destination" physicalName="ossQueue" />
改成:
<amq:topic name="destination" physicalName="ossQueue" />
就可以了。執行測試程式時,會發現在兩個服務都會收到響應資訊。
注:關於topic,有一個概念,叫“訂閱”。關於這個詞我也不是很瞭解,但我理解是:在系統中配置了amq:topic並且connectionFactory指定到對應的uri上的前提上,只要amq:topic中對應的physicalName與publish端相同,這就是訂閱,這樣的配置之後它就能收到傳送的資訊了。
總之一句話,點對點(或釋出訂閱模式)的訊息傳送關鍵在於收發雙方是否共同指定同一個physicalName。
2、自定義訊息的收發類
正如前面例子中配置檔案中的一行註釋說的一樣,訊息的收發類使用者可以自定義,它是透過MessageListenerAdapter指定訊息轉換器來實現使用者自定義的訊息收發。具體的操作我們還是來看例項吧(為了簡單起見,我們以單伺服器的配置與執行作例項,跨服務配置是一樣的):
jmsconfig.xml:
<beans xmlns=""
       xmlns:amq=""
       xmlns:xsi=""
       xsi:schemaLocation=" /spring-beans.xsd
  schema/core/activemq-core-5.0.0.xsd">
       <!-- 配置ActiveMQ服務 -->
       <amq:broker useJmx="false" persistent="false">
              <amq:transportConnectors>
                     <!-- 提供的連線方式有:VM Transport、TCP Transport、SSL Transport、
                            Peer Transport、UDP Transport、Multicast Transport、HTTP and HTTPS Transport、
                            Failover Transport、Fanout Transport、Discovery Transport、ZeroConf Transport等 -->
                     <amq:transportConnector uri="tcp://test.vemic.com:61616" />
              </amq:transportConnectors>
       </amq:broker>
       <!-- 配置JMS連線工廠(注:brokerURL是關鍵,
它應該是上面的amq:transportConnectors裡面的值之一) -->
       <amq:connectionFactory id="jmsConnectionFactory"
              brokerURL="tcp://test.vemic.com:61616" />
       <!-- 訊息傳送的目的地(注:amq:queue是用於指定是傳送topic不是queue,對應上面配置中的amq:destinations) -->
       <amq:queue name="destination" physicalName="ossQueue" />
       <!-- 建立JMS的Session生成類 -->
       <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>
              <!-- 指定傳送資訊時使用的訊息轉換類.
                     這個選項不填的話,預設的是:SimpleMessageConverter,它只支援4種型別的物件:String, byte[],Map,Serializable
              -->
              <!—如果加上下面這段配置就會出錯, 錯誤原因是Book不是一個原始類, 但我已經將它繼承Serializable了,可還是不行,我想可能有其他什麼原因吧, 但我現在不清楚 -->
              <!-- <property name="messageConverter"
                     ref="resourceMessageConverter" /> -->
       </bean>
       <!-- 傳送訊息的轉換類
(這個類要繼承org.springframework.jms.support.converter.MessageConverter) -->
       <bean id="resourceMessageConverter"
              class=" com.focustech.jms.ResourceMessageConverter" />
       <!-- 訊息生產者(透過指定目的地, 就可以同時指定其傳送的訊息模式是topic還是queue) -->
       <bean id="resourceMessageProducer"
              class=" com.focustech.jms.ResourceMessageProducer">
              <property name="template" ref="jmsTemplate" />
              <property name="destination" ref="destination" />
       </bean>
       <!-- 訊息接收類(這個類需要繼承,當然也可以透過MessageListenerAdapter指定訊息轉換器來實現使用者自定義的訊息收發) -->
       <bean id="resourceMessageListener"
              class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
              <constructor-arg>
                     <bean
                            class=" com.focustech.jms.ResourceMessageConsumer">
                     </bean>
              </constructor-arg>
              <property name="defaultListenerMethod" value="recieve" />
              <!—自定義接收類與接收的方法 -->
              <property name="messageConverter"
                     ref="resourceMessageConverter" />
       </bean>
       <!-- 訊息監聽容器,其各屬性的意義為:
              connectionFactory:指定所監聽的物件,在這裡就是監聽連線到tcp://test.vemic.com:61616上面的ActiveMQ;
              destination:監聽的訊息模式;
              messageListener:接收者
       -->
       <bean id="listenerContainer"
              class="org.springframework.jms.listener.DefaultMessageListenerContainer">
              <property name="connectionFactory" ref="jmsConnectionFactory" />
              <property name="destination" ref="destination" />
              <property name="messageListener" ref="resourceMessageListener" />
       </bean>
</beans>
在這裡,我們需要傳送自己定義的訊息格式,這樣,我們就需要不同的訊息的生產者與消費者,當然,也需要一個自定義的將兩者訊息進行轉換的一個自定義的類,如上面配置檔案中指定的一樣,這三個自定義的類的主要程式碼如下:
ResourceMessageProducer:
public class ResourceMessageProducer
{
       private JmsTemplate    template;
       private Destination      destination;
       public JmsTemplate getTemplate()
       {
              return template;
       }
       public void setTemplate(JmsTemplate template)
       {
              this.template = template;
       }
       public Destination getDestination()
       {
              return destination;
       }
       public void setDestination(Destination destination)
       {
              this.destination = destination;
       }
       public void send(Book book)
       {
              System.out.println("=======================================");
              System.out.println("do send ......");
              long l1 = System.currentTimeMillis();
              template.convertAndSend(this.destination, book);
              System.out.println("send time:" + (System.currentTimeMillis() - l1) / 1000 + "s");
              System.out.println("=======================================");
       }
}
ResourceMessageConverter:
public class ResourceMessageConverter implements MessageConverter
{
       @SuppressWarnings("unchecked")
       public Message toMessage(Object obj, Session session) throws JMSException, MessageConversionException
       {
              // check Type
              if (obj instanceof Book)
              {
                     // 採用ActiveMQ的方式傳遞訊息
                     ActiveMQObjectMessage objMsg = (ActiveMQObjectMessage) session.createObjectMessage();
                     Map map = new HashMap();
                     map.put("Book", obj);
                     // objMsg.setObjectProperty裡面放置的型別只能是:String, Map, Object, List
                     objMsg.setObjectProperty("book", map);
                     return objMsg;
              }
              else
              {
                     throw new JMSException("Object:[" + obj + "] is not Book");
              }
       }
       public Object fromMessage(Message msg) throws JMSException, MessageConversionException
       {
              if (msg instanceof ObjectMessage)
              {
                     Object obj = ((ObjectMessage) msg).getObject();
                     return obj;
              }
              else
              {
                     throw new JMSException("Msg:[" + msg + "] is not Map");
              }
       }
}
ResourceMessageConsumer:
public class ResourceMessageConsumer
{
       public void recieve(Object obj)
       {
              Book book = (Book) obj;
              System.out.println("=======================================");
              System.out.println("receiveing message ...");
              System.out.println(book.toString());
              System.out.println("here to invoke our business method...");
              System.out.println("=======================================");
       }
}
Book:
public class Book implements Serializable
{
       /**
        *
        */
       private static final long       serialVersionUID   = -6988445616774288928L;
       long                                    id;
       String                                 name;
       String                                 author;
       public String getAuthor()
       {
              return author;
       }
       public void setAuthor(String author)
       {
              this.author = author;
       }
       public long getId()
       {
              return id;
       }
       public void setId(long id)
       {
              this.id = id;
       }
       public String getName()
       {
              return name;
       }
       public void setName(String name)
       {
              this.name = name;
       }
}
訊息測試:將測試JSP中的JAVA程式碼改成:
<%
try {
     ServletContext servletContext = this.getServletContext();
     WebApplicationContext wac = WebApplicationContextUtils
      .getRequiredWebApplicationContext(servletContext);
ResourceMessageProducer resourceMessageProducer = (ResourceMessageProducer) context.getBean("messageProducer");
Book book = new Book();
book.setId(123);
book.setName("jms test!");
book.setAuthor("taofucheng");
resourceMessageProducer.send(book);
      } catch (JmsException e) {
  }
%>
執行系統,開啟測試頁面,會發現訊息已經成功接收!
注:(1)、透過這種方法,我們就可以傳送我們想傳送的任何物件了(有些限制:這些物件的型別必須是:String, Map, byte[],Serializable。上面的例子已經註釋得很清楚)。
(2)、如果大家有興趣的話,看一下MessageListenerAdapter的原始碼,你就會發現其實它就是MessageListener的實現類,在它實現的onMessage方法中使用了使用者自定義的轉換類而已。
3、整合事務
Spring提供的JMS的API中已經有了整合事務的功能,我們只要將上面監聽容器的配置改成下面的就行了:
首先,將jmsTemplate設定成支援事務(它預設是不支援事務的):
       <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>
              <property name="sessionTransacted" value="true"/>
       </bean>
然後再在訊息監聽容器中設定指定的事務管理:
    <bean id="listenerContainer"
              class="org.springframework.jms.listener.DefaultMessageListenerContainer">
              <property name="connectionFactory" ref="jmsConnectionFactory" />
              <property name="destination" ref="destination" />
              <property name="messageListener" ref="resourceMessageListener" />
              <!—jtaTransactionManager是系統中的事務管理類,在我們的系統中,是由Spring託管的 -->
              <property name="transactionManager" ref="jtaTransactionManager" />
       </bean>
這樣配置之後,當事務發生回滾時,訊息也會有回滾,即不傳送出去。
4、其它高階應用
ActiveMQ還有許多其它高階的應用,如:自動重連機制,也就是保證當通訊雙方或多方的連結斷裂後它會根據使用者的設定自動連線,以保證建立可靠的傳輸;另外,ActiveMQ還有其它方式嵌入到Spring中,如它可以透過xbean, file等方式建立應用;它還可以透過JMX對訊息的傳送與接收進行實時檢視;訊息的確認方式等等,還有很多高階的應用,請參考:《ActiveMQ in Action》(網址:http://whitesock.javaeye.com/blog/164925))




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9399028/viewspace-2108978/,如需轉載,請註明出處,否則將追究法律責任。

相關文章