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(..)'
方法如何强制类型转化为仅接受和响应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'
接受了一个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>