19.1. 簡介
Spring提供了一個(gè)用于簡化JMS API使用的抽象框架,并且對用戶屏蔽了JMS API中1.0.2和1.1版本的差異。
JMS的功能大致上分為兩塊,叫做消息制造和消息消耗。JmsTemplate用于制造消息和同步消息接收。和Java EE的事件驅(qū)動(dòng)bean風(fēng)格類似,對于異步接收消息,Spring提供了一些消息偵聽容器來創(chuàng)建消息驅(qū)動(dòng)的POJO(MDP)。
消息域的統(tǒng)一
JMS規(guī)范有兩個(gè)主要的版本,1.0.2和1.1。
JMS1.0.2定義了兩種消息域,點(diǎn)對點(diǎn)(隊(duì)列)和發(fā)布/訂閱(主題)。JMS 1.0.2的API為每個(gè)消息域提供了一個(gè)平行的類層次結(jié)構(gòu)。導(dǎo)致客戶端應(yīng)用只能使用特定消息域的JMS API。JMS 1.1引進(jìn)了統(tǒng)一消息域的概念使這兩種消息域之間功能和客戶端API的差別盡可能小。舉個(gè)已消除的功能差異的例子,如果你使用的是JMS 1.1的消息供應(yīng)者,你可以使用同一個(gè)Session事務(wù)性地在一個(gè)域消耗了一個(gè)消息后并且在另一個(gè)域中產(chǎn)生一個(gè)消息。
JMS 1.1的規(guī)范發(fā)布于2002年4月,并且在2003年11月成為J2EE 1.4的一個(gè)組成部分,結(jié)果,現(xiàn)在大多數(shù)使用的應(yīng)用服務(wù)器只支持JMS 1.0.2的規(guī)范.
org.springframework.jms.core包提供使用JMS的核心功能。 就象為JDBC提供的JdbcTemplate一樣,它提供了JMS模板類來處理資源的創(chuàng)建和釋放以簡化JMS的使用。Spring模板類的公共設(shè)計(jì)原則就是通過提供助手方法去執(zhí)行公共的操作,并將實(shí)際的處理任務(wù)委派到用戶實(shí)現(xiàn)的回調(diào)接口上,從而完成更復(fù)雜的操作。 JMS模板也遵循這樣的設(shè)計(jì)原則。這些類提供眾多便利的方法來發(fā)送消息、同步接收消息、 使用戶可以接觸到JMS session和消息產(chǎn)生者。
org.springframework.jms.support包提供JMSException的轉(zhuǎn)換功能。它將受控的 JMSException異常層次轉(zhuǎn)換到一個(gè)對應(yīng)的非受控異常層次。任何受控javax.jms.JMSException異常的子類都被包裝在非受控UncategorizedJmsException異常里。
org.springframework.jms.support.converter 包提供一個(gè)MessageConverter用來抽象Java對象和JMS消息之間的轉(zhuǎn)換操作。
包org.springframework.jms.support.destination為管理JMS目的地提供多種策略,例如為存儲在JNDI中的目的地提供一個(gè)服務(wù)定位器。
最后,org.springframework.jms.connection包提供一個(gè)適合在獨(dú)立應(yīng)用中使用的ConnectionFactory的實(shí)現(xiàn)。它還為JMS提供了一個(gè)Spring的PlatformTransactionManager的實(shí)現(xiàn)(現(xiàn)在叫做JmsTransactionManager)。 這樣可以把JMS作為一個(gè)事務(wù)資源無縫地集成到Spring的事務(wù)管理機(jī)制中去。
19.2. 使用Spring JMS
19.2.1. JmsTemplate
JmsTemplate類有兩個(gè)實(shí)現(xiàn)方式。JmsTemplate類使用JMS 1.1的API, 而子類JmsTemplate102使用了JMS 1.0.2的API。
使用JmsTemplate的代碼只需要實(shí)現(xiàn)規(guī)范中定義的回調(diào)接口。 MessageCreator回調(diào)接口通過JmsTemplate中調(diào)用代碼提供的Session來創(chuàng)建一條消息。 然而,為了允許更復(fù)雜的JMS API應(yīng)用,回調(diào)接口SessionCallback為用戶提供JMS session,并且回調(diào)接口ProducerCallback將Session和MessageProducer對顯露給用戶。
JMS API有兩種發(fā)送方法,一種采用發(fā)送模式、優(yōu)先級和存活時(shí)間作為服務(wù)質(zhì)量(QOS)參數(shù),另一種使用無需QOS參數(shù)的缺省值方法。由于在JmsTemplate中有許多種發(fā)送方法,QOS參數(shù)通過bean的屬性方式進(jìn)行設(shè)置,從而避免在多種發(fā)送方法中重復(fù)。同樣,使用setReceiveTimeout屬性值來設(shè)置同步接收調(diào)用的超時(shí)值。
某些JMS供應(yīng)者允許通過ConnectionFactory的配置來設(shè)置缺省的QOS值。這樣在調(diào)用MessageProducer的發(fā)送方法send(Destination destination, Message message)時(shí)會(huì)使用那些不同的QOS缺省值,而不是JMS規(guī)范中定義的值。所以,為了提供對QOS值的一致管理,JmsTemplate必須通過設(shè)置布爾值屬性isExplicitQosEnabled為true,使它能夠使用自己的QOS值。
19.2.2. 連接工廠
JmsTemplate需要一個(gè)對ConnectionFactory的引用。ConnectionFactory是JMS規(guī)范的一部分,并且是使用JMS的入口??蛻舳藨?yīng)用通常用它作工廠配合JMS提供者去創(chuàng)建連接,并封裝許多和供應(yīng)商相關(guān)的配置參數(shù),例如SSL的配置選項(xiàng)。
當(dāng)在EJB里使用JMS時(shí),供應(yīng)商會(huì)提供JMS接口的實(shí)現(xiàn),這樣們可以參與聲明式事務(wù)管理并 提供連接池和會(huì)話池。為了使用這個(gè)JMS實(shí)現(xiàn),Java EE容器通常要求你在EJB或servlet部署描述符中聲明一個(gè)JMS連接工廠做為 resource-ref。為確??梢栽贓JB內(nèi)使用JmsTemplate的這些特性, 客戶應(yīng)用應(yīng)當(dāng)確保它引用了被管理的ConnectionFactory實(shí)現(xiàn)。
Spring提供了一個(gè)ConnectionFactory接口的實(shí)現(xiàn),SingleConnectionFactory,它將在所有的createConnection調(diào)用中返回一個(gè)相同的Connection,并忽略所有對close的調(diào)用。這在測試和獨(dú)立環(huán)境中相當(dāng)有用,因?yàn)槎鄠€(gè)JmsTemplate調(diào)用可以使用同一個(gè)連接以跨越多個(gè)事務(wù)。SingleConnectionFactory通常引用一個(gè)來自JNDI的標(biāo)準(zhǔn)ConnectionFactory。
19.2.3. (消息)目的地管理
和連接工廠一樣,目的地是可以在JNDI中存儲和獲取的JMS管理的對象。配置一個(gè)Spring應(yīng)用上下文時(shí),可以使用JNDI工廠類JndiObjectFactoryBean把對你對象的引用依賴注入到JMS目的地中。然而,如果在應(yīng)用中有大量的目的地,或者JMS供應(yīng)商提供了特有的高級目的地管理特性,這個(gè)策略常常顯得很麻煩。創(chuàng)建動(dòng)態(tài)目的地或支持目的地的命名空間層次就是這種高級目的地管理的例子。JmsTemplate將目的地名稱到JMS目的地對象的解析委派給DestinationResolver接口的一個(gè)實(shí)現(xiàn)。JndiDestinationResolver是JmsTemplate使用的默認(rèn)實(shí)現(xiàn),并且提供動(dòng)態(tài)目的地解析。同時(shí)JndiDestinationResolver作為JNDI中的目的地服務(wù)定位器,還可選擇回退去使用DynamicDestinationResolver中的行為。
經(jīng)常見到一個(gè)JMS應(yīng)用中使用的目的地在運(yùn)行時(shí)才知道,因此,當(dāng)部署一個(gè)應(yīng)用時(shí),它不能用可管理的方式創(chuàng)建。這是經(jīng)常發(fā)生的,因?yàn)樵诨ハ嘧饔玫南到y(tǒng)組件間有些共享應(yīng)用邏輯會(huì)在運(yùn)行的時(shí)候按照共同的命名規(guī)范創(chuàng)建消息目的地。雖然動(dòng)態(tài)創(chuàng)建目的地不是JMS規(guī)范的一部分,但是大多數(shù)供應(yīng)商已經(jīng)提供了這個(gè)功能。 用戶為動(dòng)態(tài)創(chuàng)建的目的地定義和臨時(shí)目的地不同的名字,并且通常不被注冊到JNDI中。不同供應(yīng)商創(chuàng)建動(dòng)態(tài)消息目的地所使用的API差異很大,因?yàn)楹湍康牡叵嚓P(guān)的屬性是供應(yīng)商特有的。然而,有時(shí)由供應(yīng)商會(huì)作出一個(gè)簡單的實(shí)現(xiàn)選擇-忽略JMS規(guī)范中的警告,使用TopicSession的方法createTopic(String topicName)或者QueueSession的方法createQueue(String queueName)來創(chuàng)建一個(gè)帶默認(rèn)值屬性的新目的地。依賴于供應(yīng)商的實(shí)現(xiàn),DynamicDestinationResolver也可能創(chuàng)建一個(gè)物理上的目的地,而不只是一個(gè)解析。
布爾屬性pubSubDomain用來配置JmsTemplate使用什么樣的JMS域。這個(gè)屬性的默認(rèn)值是false,使用點(diǎn)到點(diǎn)的域,也就是隊(duì)列。在1.0.2的實(shí)現(xiàn)中,這個(gè)屬性值用來決定JmsTemplate將消息發(fā)送到一個(gè)Queue還是一個(gè)Topic。這個(gè)標(biāo)志在1.1的實(shí)現(xiàn)中對發(fā)送操作沒有影響。然而,在這兩個(gè)JMS版本中,這個(gè)屬性決定了通過接口DestinationResolver的實(shí)現(xiàn)來決定如何解析動(dòng)態(tài)消息目的地。
你還可以通過屬性defaultDestination配置一個(gè)帶有默認(rèn)目的地的JmsTemplate。不指明目的地的發(fā)送和接受操作將使用該默認(rèn)目的地。
19.2.4. 消息偵聽容器
在EJB世界里,JMS消息最常用的功能之一是用于實(shí)現(xiàn)消息驅(qū)動(dòng)bean(MDBs)。Spring提供了一個(gè)方法來創(chuàng)建消息驅(qū)動(dòng)的POJO(MDPs),并且不會(huì)把用戶綁定在某個(gè)EJB容器上。(關(guān)于Spring的MDP支持的細(xì)節(jié)請參考標(biāo)題為
Section 19.4.2, “異步接收 - 消息驅(qū)動(dòng)的POJOs”的節(jié))
通常用AbstractMessageListenerContainer的一個(gè)子類從JMS消息隊(duì)列接收消息并驅(qū)動(dòng)被注射進(jìn)來的MDP。AbstractMessageListenerContainer負(fù)責(zé)消息接收的多線程處理并分發(fā)到各MDP中。一個(gè)消息偵聽容器是MDP和消息提供者之間的一個(gè)中介,用來處理消息接收的注冊,事務(wù)管理的參與,資源獲取和釋放,異常轉(zhuǎn)換等等。這使得應(yīng)用開發(fā)人員可以專注于開發(fā)和接收消息(可能的響應(yīng))相關(guān)的(復(fù)雜)業(yè)務(wù)邏輯,把和JMS基礎(chǔ)框架有關(guān)的樣板化的部分委托給框架處理。
Spring提供了三種AbstractMessageListenerContainer的子類,每種各有其特點(diǎn)。
19.2.4.1. SimpleMessageListenerContainer
這個(gè)消息偵聽容器是三種中最簡單的。它在啟動(dòng)時(shí)創(chuàng)建固定數(shù)量的JMS session并在容器的整個(gè)生命周期中使用它們。這個(gè)類不能動(dòng)態(tài)的適應(yīng)運(yùn)行時(shí)的要求或參與消息接收的事務(wù)處理。然而它對JMS提供者的要求也最低。它只需要簡單的JMS API。
19.2.4.2. DefaultMessageListenerContainer
這個(gè)消息偵聽器使用的最多。和SimpleMessageListenerContainer一樣,這個(gè)子類不能動(dòng)態(tài)適應(yīng)運(yùn)行時(shí)侯的要求。然而,它可以參與事務(wù)管理。每個(gè)收到的消息都注冊到一個(gè)XA事務(wù)中(如果配置過),這樣就可以利用XA事務(wù)語義的優(yōu)勢了。這個(gè)類在對JMS提供者的低要求和提供包括事務(wù)參于等的強(qiáng)大功能上取得了很好的平衡。
19.2.4.3. ServerSessionMessageListenerContainer
這個(gè)子類是三者中最強(qiáng)大的。它利用JMS ServerSessionPool SPI允許對JMS session進(jìn)行動(dòng)態(tài)管理。它也支持事務(wù)。使用這種消息偵聽器可以獲得強(qiáng)大的運(yùn)行時(shí)調(diào)優(yōu)功能,但是對使用到的JMS提供者有很高的要求(ServerSessionPool SPI)。如果不需要運(yùn)行時(shí)的性能調(diào)整,請使用DefaultMessageListenerContainer或SimpleMessageListenerContainer。
19.2.5. 事務(wù)管理
Spring提供了一個(gè)JmsTransactionManager為單個(gè)JMSConnectionFactory管理事務(wù)。這將允許JMS應(yīng)用利用
Chapter 9, 事務(wù)管理中描述的Spring的事務(wù)管理功能。JmsTransactionManager從指定的ConnectionFactory綁定了一個(gè)Connection/Session對到線程上。然而,在Java EE環(huán)境中,SingleConnectionFactory將把連接和session放到緩沖池中,所以綁定到線程的實(shí)例將依賴越緩沖池的行為。在標(biāo)準(zhǔn)環(huán)境下,使用Spring的SingleConnectionFactory將使得和每個(gè)事務(wù)相關(guān)的JMS連接有自己的session。JmsTemplate也可以和JtaTransactionManager以及具有XA能力的JMS ConnectionFactory一起使用來提供分布式交易。
當(dāng)使用JMS API從一個(gè)連接中創(chuàng)建session時(shí),在受管理的和非受管理的事務(wù)環(huán)境下重用代碼會(huì)可能會(huì)讓人迷惑。這是因?yàn)镴MS API只有一個(gè)工廠方法來創(chuàng)建session并且它需要用于事務(wù)和模式確認(rèn)的值。在受管理的環(huán)境下,由事務(wù)結(jié)構(gòu)環(huán)境負(fù)責(zé)設(shè)置這些值,這樣在供應(yīng)商包裝的JMS連接中可以忽略這些值。當(dāng)在一個(gè)非管理性的環(huán)境中使用JmsTemplate時(shí),你可以通過使用屬性SessionTransacted和SessionAcknowledgeMode來指定這些值。當(dāng)配合 JmsTemplate中使用PlatformTransactionManager時(shí),模板將一直被賦予一個(gè)事務(wù)性JMS的 Session。
19.3. 發(fā)送一條消息
JmsTemplate包含許多方便的方法來發(fā)送消息。有些發(fā)送方法可以使用 javax.jms.Destination對象指定目的地,也可以使用字符串在JNDI中查找目的地。沒有目的地參數(shù)的發(fā)送方法使用默認(rèn)的目的地。這里有個(gè)例子使用1.0.2版的JMS實(shí)現(xiàn)發(fā)送消息到一個(gè)隊(duì)列。
import javax.jms.ConnectionFactory;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.Queue;import javax.jms.Session;import org.springframework.jms.core.MessageCreator;import org.springframework.jms.core.JmsTemplate;import org.springframework.jms.core.JmsTemplate102;public class JmsQueueSender { private JmsTemplate jmsTemplate; private Queue queue; public void setConnectionFactory(ConnectionFactory cf) { this.jmsTemplate = new JmsTemplate102(cf, false); } public void setQueue(Queue queue) { this.queue = queue; } public void simpleSend() { this.jmsTemplate.send(this.queue, new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createTextMessage("hello queue world"); } }); }}這個(gè)例子使用MessageCreator回調(diào)接口從提供的Session對象中創(chuàng)建一個(gè)文本消息,并且通過一個(gè)ConnectionFactory的引用和指定消息域的布爾值來創(chuàng)建JmsTemplate。提供了一個(gè)無參數(shù)的構(gòu)造方法和connectionFactory / queuebean屬性并可用于創(chuàng)建實(shí)例(使用一個(gè)BeanFactory或者普通Java 代碼code)?;蛘呖紤]從Spring的基類JmsGatewaySupport,它對JMS配置具有內(nèi)置的bean屬性,繼承一個(gè)類。
當(dāng)在應(yīng)用上下文中配置JMS 1.0.2時(shí),重要的是記得設(shè)定布爾屬性pubSubDomain的值以指明你是要發(fā)送到隊(duì)列還是主題。
方法send(String destinationName, MessageCreator creator)讓你利用目的地的字符串名字發(fā)送消息。如果這個(gè)名字在JNDI中注冊,你應(yīng)當(dāng)將模板中的destinationResolver屬性設(shè)置為JndiDestinationResolver的一個(gè)實(shí)例。
如果你創(chuàng)建JmsTemplate并指定一個(gè)默認(rèn)的目的地,send(MessageCreator c)發(fā)送消息到這個(gè)目的地。
19.3.1. 使用消息轉(zhuǎn)換器
為便于發(fā)送領(lǐng)域模型對象,JmsTemplate有多種以一個(gè)Java對象為參數(shù)并做為消息數(shù)據(jù)內(nèi)容的發(fā)送方法。JmsTemplate里可重載的方法convertAndSend和receiveAndConvert將轉(zhuǎn)換的過程委托給接口MessageConverter的一個(gè)實(shí)例。這個(gè)接口定義了一個(gè)簡單的合約用來在Java對象和JMS消息間進(jìn)行轉(zhuǎn)換。缺省的實(shí)現(xiàn)SimpleMessageConverter支持String和TextMessage,byte[]和BytesMesssage,以及java.util.Map和MapMessage之間的轉(zhuǎn)換。使用轉(zhuǎn)換器,可以使你和你的應(yīng)用關(guān)注于通過JMS接收和發(fā)送的業(yè)務(wù)對象而不用操心它是具體如何表達(dá)成JMS消息的。
目前的沙箱模型包括一個(gè)MapMessageConverter,它使用反射轉(zhuǎn)換JavaBean和MapMessage。其他流行可選的實(shí)現(xiàn)方式包括使用已存在的XML編組的包,例如JAXB,Castor, XMLBeans, 或XStream的轉(zhuǎn)換器來創(chuàng)建一個(gè)表示對象的TextMessage。
為方便那些不能以通用方式封裝在轉(zhuǎn)換類里的消息屬性,消息頭和消息體的設(shè)置,通過MessagePostProcessor接口你可以在消息被轉(zhuǎn)換后并且在發(fā)送前訪問該消息。下例展示了如何在java.util.Map已經(jīng)轉(zhuǎn)換成一個(gè)消息后更改消息頭和屬性。
public void sendWithConversion() { Map m = new HashMap(); m.put("Name", "Mark"); m.put("Age", new Integer(47)); jmsTemplate.convertAndSend("testQueue", m, new MessagePostProcessor() { public Message postProcessMessage(Message message) throws JMSException { message.setIntProperty("AccountID", 1234); message.setJMSCorrelationID("123-00001"); return message; } });}This results in a message of the form:
這將產(chǎn)生一個(gè)如下的消息格式:
MapMessage={ Header={ ... standard headers ... CorrelationID={123-00001} } Properties={ AccountID={Integer:1234} } Fields={ Name={String:Mark} Age={Integer:47} } }
19.3.2. SessionCallback 和ProducerCallback
雖然send操作適用于許多常見的使用場景,但是有時(shí)你需要在一個(gè)JMS Session或者M(jìn)essageProducer上執(zhí)行多個(gè)操作。接口SessionCallback和ProducerCallback分別提供了JMS Session和Session / MessageProducer對。JmsTemplate上的execute()方法執(zhí)行這些回調(diào)方法。
19.4. 接收消息
19.4.1. 同步接收
雖然JMS一般都和異步處理相關(guān),但它也可以同步的方式使用消息??芍剌d的receive(..)方法提供了這種功能。在同步接收中,接收線程被阻塞直至獲得一個(gè)消息,有可能出現(xiàn)線程被無限阻塞的危險(xiǎn)情況。屬性receiveTimeout指定了接收器可等待消息的延時(shí)時(shí)間。
19.4.2. 異步接收 - 消息驅(qū)動(dòng)的POJOs
類似于EJB世界里流行的消息驅(qū)動(dòng)bean(MDB),消息驅(qū)動(dòng)POJO(MDP)作為JMS消息的接收器。MDP的一個(gè)約束(但也請看下面的有關(guān)javax.jms.MessageListener類的討論)是它必須實(shí)現(xiàn)javax.jms.MessageListener接口。另外當(dāng)你的POJO將以多線程的方式接收消息時(shí)必須確保你的代碼是線程-安全的。
以下是MDP的一個(gè)簡單實(shí)現(xiàn):
import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;public class ExampleListener implements MessageListener { public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println(((TextMessage) message).getText()); } catch (JMSException ex) { throw new RuntimeException(ex); } } else { throw new IllegalArgumentException("Message must be of type TextMessage"); } }}一旦你實(shí)現(xiàn)了MessageListener后就可以創(chuàng)建一個(gè)消息偵聽容器。
請看下面例子是如何定義和配置一個(gè)隨Sping發(fā)行的消息偵聽容器的(這個(gè)例子用DefaultMessageListenerContainer)
<!-- this is the Message Driven POJO (MDP) --><bean id="messageListener" class="jmsexample.ExampleListener" /><!-- and this is the attendant message listener container --><bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="concurrentConsumers" value="5"/> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="destination" /> <property name="messageListener" ref="messageListener" /></bean>關(guān)于各個(gè)消息偵聽容器實(shí)現(xiàn)的特色請參閱相關(guān)的Spring Javadoc文檔。
19.4.3. SessionAwareMessageListener 接口
SessionAwareMessageListener接口是一個(gè)Spring專門用來提供類似于JMS MessageListener的接口,也提供了從接收Message來訪問JMS Session的消息處理方法。
package org.springframework.jms.listener;public interface SessionAwareMessageListener { void onMessage(Message message, Session session) throws JMSException;}如果你希望你的MDP可以響應(yīng)所有接收到的消息(使用onMessage(Message, Session)方法提供的Session)那么你可以選擇讓你的MDP實(shí)現(xiàn)這個(gè)接口(優(yōu)先于標(biāo)準(zhǔn)的JMSMessageListener接口)。所有隨Spring發(fā)行的支持MDP的消息偵聽容器都支持MessageListener或SessionAwareMessageListener接口的實(shí)現(xiàn)。要注意的是實(shí)現(xiàn)了SessionAwareMessageListener接口的類通過接口和Spring有了耦合。是否選擇使用它完全取決于開發(fā)者或架構(gòu)師。
請注意SessionAwareMessageListener接口的'onMessage(..)'方法會(huì)拋出JMSException異常。和標(biāo)準(zhǔn)JMS MessageListener接口相反,當(dāng)使用SessionAwareMessageListener接口時(shí),客戶端代碼負(fù)責(zé)處理任何拋出的異常。
19.4.4. MessageListenerAdapter
MessageListenerAdapter類是Spring的異步支持消息類中的不變類(final class):簡而言之,它允許你幾乎將任意一個(gè)類做為MDP顯露出來(當(dāng)然有某些限制)。
Note
如果你使用JMS 1.0.2 API,你將使用和MessageListenerAdapter一樣功能的類MessageListenerAdapter102。
考慮如下接口定義。注意雖然這個(gè)接口既不是從MessageListener也不是從SessionAwareMessageListener繼承來得,但通過MessageListenerAdapter類依然可以當(dāng)作一個(gè)MDP來使用。同時(shí)也請注意各種消息處理方法是如何根據(jù)他們可以接收并處理消息的內(nèi)容來進(jìn)行強(qiáng)類型匹配的。
public interface MessageDelegate { void handleMessage(String message); void handleMessage(Map message); void handleMessage(byte[] message); void handleMessage(Serializable message);}public class DefaultMessageDelegate implements MessageDelegate { // implementation elided for clarity...}特別請注意,上面的MessageDelegate接口(上文中DefaultMessageDelegate類)的實(shí)現(xiàn)完全不依賴于JMS。它是一個(gè)真正的POJO,我們可以通過如下配置把它設(shè)置成MDP。
<!-- this is the Message Driven POJO (MDP) --><bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultMessageDelegate"/> </constructor-arg></bean><!-- and this is the attendant message listener container... --><bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="concurrentConsumers" value="5"/> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="destination" /> <property name="messageListener" ref="messageListener" /></bean>下面是另外一個(gè)只能處理接收J(rèn)MSTextMessage消息的MDP示例。注意消息處理方法是如何實(shí)際調(diào)用'receive'(在MessageListenerAdapter中默認(rèn)的消息處理方法的名字是'handleMessage')的,但是它是可配置的(你下面就將看到)。注意'receive(..)'方法是如何使用強(qiáng)制類型來只接收和處理JMS TextMessage消息的。
public interface TextMessageDelegate { void receive(TextMessage message);}public class DefaultTextMessageDelegate implements TextMessageDelegate { // implementation elided for clarity...}輔助的MessageListenerAdapter類配置文件類似如下:
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultTextMessageDelegate"/> </constructor-arg> <property name="defaultListenerMethod" value="receive"/> <!-- we don't want automatic message context extraction --> <property name="messageConverter"> <null/> </property></bean>請注意,如果上面的'messageListener'收到一個(gè)不是TextMessage類型的JMS Message,將會(huì)產(chǎn)生一個(gè)IllegalStateException異常(隨之產(chǎn)生的其他異常只被捕獲而不處理)。
MessageListenerAdapter還有一個(gè)功能就是如果處理方法返回一個(gè)非空值,它將自動(dòng)返回一個(gè)響應(yīng)消息。
請看下面的接口及其實(shí)現(xiàn):
public interface ResponsiveTextMessageDelegate { // notice the return type... String receive(TextMessage message);}public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate { // implementation elided for clarity...}如果上面的DefaultResponsiveTextMessageDelegate和MessageListenerAdapter聯(lián)合使用,那么任意從執(zhí)行'receive(..)'方法返回的非空值都將(缺省情況下)轉(zhuǎn)換成一個(gè)TextMessage。這個(gè)返回的TextMessage將被發(fā)送到原來的Message中JMS Reply-To屬性定義的目的地(如果存在),或者是MessageListenerAdapter設(shè)置(如果配置了)的缺省目的地;如果沒有定義目的地,那么將產(chǎn)生一個(gè)InvalidDestinationException異常(此異常將不會(huì)只被捕獲而不處理,它將沿著調(diào)用堆棧上傳)。
19.4.5. 事務(wù)中的多方參與
參與到事務(wù)中只需要一點(diǎn)微小的改動(dòng)。你需要?jiǎng)?chuàng)建一個(gè)事務(wù)管理器,并且注冊到一個(gè)可以參與事務(wù)的子類中(DefaultMessageListenerContainer或ServerSessionMessageListenerContainer)。
為了創(chuàng)建事務(wù)管理器,你需要?jiǎng)?chuàng)建一個(gè)JmsTransactionManager的實(shí)例并提供給它一個(gè)支持XA事務(wù)功能的連接工廠。
<bean id="transactionManager" class="org.springframework.jms.connection.JmsTransactionManager"> <property name="connectionFactory" ref="connectionFactory" /></bean>然后你只需要把它加入到我們先前的容器配置中。容器會(huì)處理其他的事情。
<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="concurrentConsumers" value="5" /> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="destination" /> <property name="messageListener" ref="messageListener" /> <property name="transactionManager" ref="transactionManager" /></bean>