23.4.1 同步接受
JMS一般是異步處理,也有可能同步消費消息。重載receive(..)
方法提供了這個功能。在同步接受期間,調用線程會一直阻塞直到消息可用。這會是很危險的操作,因爲調用線程可能隨機發生阻塞。receiveTimeout
屬性指定了接收器在放棄等待一條消息前應該等待的時間。
23.4.2 異步接受---消息驅動 POJOs
需注意Spring通過使用@JmsListener
也提供註解監聽端並提供一個開放底層程序化註冊端點。這是迄今爲止設置異步接收器最方便的方式。
與EJB中MDB相似,JMS 消息使用消息驅動的POJO即MDP作爲接收器。MDP的一個約束是必須要實現javax.jms.MessageListener
接口。請注意這種情況,你的POJO在多線程下接受消息時,確保線程安全很重要。
下面是一個簡單的MDP的實現:
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");
}
}
}
只要實現了
MessageListener
接口,就該創建一個消息監聽容器了。
找出下面的例子中如何在Spring中定義和配置一個消息監聽容器的(這裏是DefaultMessageListenerContainer
)
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener" />
<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener" />
</bean>
23.4.3 SessionAwareMessageListener接口
SessionAwareMessageListener是Spring指定的接口,用於提供與JMS
MessageListener
接口相同的功能,但是也提供給消息處理方法,藉此從消息接受的對象中訪問JMS會話。
package org.springframework.jms.listener;
public interface SessionAwareMessageListener {
void onMessage(Message message, Session session) throws JMSException;
}
你可以選擇性地使你的MDPs實現這個接口(參考標準的JMS
MessageListener
接口),如果你想你的MDPs能夠響應任何接受的消息(使用onMessage(Message, Session)
方法中的Session)。所有的Spring的消息監聽容器支持MDPs實現MessageListener
或者SessionAwareMessageListener
接口。實現了SessionAwareMessageListener
接口的類有點瑕疵,因爲其與Spring耦合。是否選擇使用這個接口完全取決於應用程序開發者或架構師。
請注意
SessionAwareMessageListener
接口的'onMessage(..)'
方法拋出了JMSException
。與標準的JMSMessageListener
接口比較,當使用SessionAwareMessageListener
接口時,客戶端代碼負責處理任何異常拋出。
23.4.4 MessageListenerAdapter
MessageListenerAdapter
類是Spring異步消息支持中的最終組件:概括性說,其允許你暴露幾乎任何類作爲MDP(當然了也有一些約束)。
考慮下面的接口定義。注意到這個接口既不繼承MessageListener
,也不繼承SessionAwareMessageListener
接口,藉助MessageListenerAdapter
類,也可以用作MDP。也要注意到不同的消息處理方法根據不同的Message類型的內容(其可以接受和處理的額)如何強制性類型轉換。
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
類)沒有任何的JMS依賴。完全是一個POJO,通過以下配置轉換爲一個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 message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener" />
</bean>
下面是另一個MDP的例子,其能處理JMS
TextMessage
消息的接受。注意消息處理方法如何調用'receive'(MessageListenerAdapter
類中消息處理方法名默認爲'handleMessage'
) ,但是它是可配置的(如下所示)。也注意到'receive(..)'
方法如何強制類型轉化爲僅接受和響應JMSTextMessage
消息。
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'
接受了一個JMS 消息,而不是TextMessage
類型,將拋出一個IllegalStateException
異常。MessageListenerAdapter
類的另一個功能是自動發送回一個響應消息,如果處理器方法返回一個有效值的話。看下面的接口和類:
public interface ResponsiveTextMessageDelegate {
// notice the return type...
String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
// implementation elided for clarity...
}
如果上述的
DefaultResponsiveTextMessageDelegate
與MessageListenerAdapter
聯合使用,從執行的'receive(..)
方法中返回一個非空值,將轉換爲一個TextMessage
。結果TextMessage
將在稍後發送到Destination(如果存在),其在JMS
的初始Message
的Reply-To屬性中定義,或者MessageListenerAdapter
定義的默認的Destination
(如果已經配置了);如果沒有發現Destination
,則拋出異常InvalidDestinationException
(請注意這個異常將不被容忍,並將傳送到調用回調)。
23..4.5 事物內處理消息
在一個事物中調用消息監聽器僅僅需要重新配置監聽容器。
定位資源事物可以通過監聽容器定義的sessionTransacted
標示激活。每個消息監聽調用將在一個活性的JMS事物中執行,並在監聽執行失敗時,消息接受會回滾。發送一個響應消息(藉助SessionAwareMessageListener
)將是相同本地事物一部分,但是另外的資源操作(比如數據庫訪問)將分開操作。這通常需要在監聽實現中進行多重的消息查找,囊括了這種情況,提交了數據庫處理但是消息處理提交失敗。
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="sessionTransacted" value="true"/>
</bean>
對於外部管理事物中的多人蔘與情況,你需要配置一個事物管理器並使用一個監聽容器,其支持外部事物管理:一般的是
DefaultMessageListenerContainer
。
對於XA事物參與配置的消息監聽容器,你將想要配置一個JtaTransactionManager
(默認地,委託給Java EE服務器的事物子系統)。注意到底層的JMS ConnectionFactory需要是XA-capable並註冊你的JTA 事物協調器。
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
之後 你只需要將其添加到你早期的容器配置中。容器就負責剩餘的工作了:
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>