19.2. 使用Spring JMS

19.2.1. JmsTemplate

JmsTemplate类有两个实现方式。JmsTemplate类使用JMS 1.1的API, 而子类JmsTemplate102使用了JMS 1.0.2的API。

使用JmsTemplate的代码只需要实现规范中定义的回调接口。 MessageCreator回调接口通过JmsTemplate中调用代码提供的Session来创建一条消息。 然而,为了允许更复杂的JMS API应用,回调接口SessionCallback为用户提供JMS session,并且回调接口ProducerCallback将Session和MessageProducer对显露给用户。

JMS API有两种发送方法,一种采用发送模式、优先级和存活时间作为服务质量(QOS)参数,另一种使用无需QOS参数的缺省值方法。由于在JmsTemplate中有许多种发送方法,QOS参数通过bean的属性方式进行设置,从而避免在多种发送方法中重复。同样,使用setReceiveTimeout属性值来设置同步接收调用的超时值。

某些JMS供应者允许通过ConnectionFactory的配置来设置缺省的QOS值。这样在调用MessageProducer的发送方法send(Destination destination, Message message)时会使用那些不同的QOS缺省值,而不是JMS规范中定义的值。所以,为了提供对QOS值的一致管理,JmsTemplate必须通过设置布尔值属性isExplicitQosEnabled为true,使它能够使用自己的QOS值。

19.2.2. 连接工厂

JmsTemplate需要一个对ConnectionFactory的引用。ConnectionFactory是JMS规范的一部分,并且是使用JMS的入口。客户端应用通常用它作工厂配合JMS提供者去创建连接,并封装许多和供应商相关的配置参数,例如SSL的配置选项。

当在EJB里使用JMS时,供应商会提供JMS接口的实现,这样们可以参与声明式事务管理并 提供连接池和会话池。为了使用这个JMS实现,Java EE容器通常要求你在EJB或servlet部署描述符中声明一个JMS连接工厂做为 resource-ref。为确保可以在EJB内使用JmsTemplate的这些特性, 客户应用应当确保它引用了被管理的ConnectionFactory实现。

Spring提供了一个ConnectionFactory接口的实现,SingleConnectionFactory,它将在所有的createConnection调用中返回一个相同的Connection,并忽略所有对close的调用。这在测试和独立环境中相当有用,因为多个JmsTemplate调用可以使用同一个连接以跨越多个事务。SingleConnectionFactory通常引用一个来自JNDI的标准ConnectionFactory

19.2.3. (消息)目的地管理

和连接工厂一样,目的地是可以在JNDI中存储和获取的JMS管理的对象。配置一个Spring应用上下文时,可以使用JNDI工厂类JndiObjectFactoryBean把对你对象的引用依赖注入到JMS目的地中。然而,如果在应用中有大量的目的地,或者JMS供应商提供了特有的高级目的地管理特性,这个策略常常显得很麻烦。创建动态目的地或支持目的地的命名空间层次就是这种高级目的地管理的例子。JmsTemplate将目的地名称到JMS目的地对象的解析委派给DestinationResolver接口的一个实现。JndiDestinationResolverJmsTemplate 使用的默认实现,并且提供动态目的地解析。同时JndiDestinationResolver作为JNDI中的目的地服务定位器,还可选择回退去使用DynamicDestinationResolver中的行为。

经常见到一个JMS应用中使用的目的地在运行时才知道,因此,当部署一个应用时,它不能用可管理的方式创建。这是经常发生的,因为在互相作用的系统组件间有些共享应用逻辑会在运行的时候按照共同的命名规范创建消息目的地。虽然动态创建目的地不是JMS规范的一部分,但是大多数供应商已经提供了这个功能。 用户为动态创建的目的地定义和临时目的地不同的名字,并且通常不被注册到JNDI中。不同供应商创建动态消息目的地所使用的API差异很大,因为和目的地相关的属性是供应商特有的。然而,有时由供应商会作出一个简单的实现选择-忽略JMS规范中的警告,使用TopicSession的方法createTopic(String topicName)或者QueueSession的方法createQueue(String queueName)来创建一个带默认值属性的新目的地。依赖于供应商的实现,DynamicDestinationResolver也可能创建一个物理上的目的地,而不只是一个解析。

布尔属性pubSubDomain用来配置JmsTemplate使用什么样的JMS域。这个属性的默认值是false,使用点到点的域,也就是队列。在1.0.2的实现中,这个属性值用来决定JmsTemplate将消息发送到一个Queue还是一个Topic。这个标志在1.1的实现中对发送操作没有影响。然而,在这两个JMS版本中,这个属性决定了通过接口DestinationResolver的实现来决定如何解析动态消息目的地。

你还可以通过属性defaultDestination配置一个带有默认目的地的JmsTemplate。不指明目的地的发送和接受操作将使用该默认目的地。

19.2.4. 消息侦听容器

在EJB世界里,JMS消息最常用的功能之一是用于实现消息驱动bean(MDBs)。Spring提供了一个方法来创建消息驱动的POJO(MDPs),并且不会把用户绑定在某个EJB容器上。(关于Spring的MDP支持的细节请参考标题为第 19.4.2 节 “异步接收 - 消息驱动的POJOs”的节)

通常用AbstractMessageListenerContainer的一个子类从JMS消息队列接收消息并驱动被注射进来的MDP。AbstractMessageListenerContainer负责消息接收的多线程处理并分发到各MDP中。一个消息侦听容器是MDP和消息提供者之间的一个中介,用来处理消息接收的注册,事务管理的参与,资源获取和释放,异常转换等等。这使得应用开发人员可以专注于开发和接收消息(可能的响应)相关的(复杂)业务逻辑,把和JMS基础框架有关的样板化的部分委托给框架处理。

Spring提供了三种AbstractMessageListenerContainer的子类,每种各有其特点。

19.2.4.1. SimpleMessageListenerContainer

这个消息侦听容器是三种中最简单的。它在启动时创建固定数量的JMS session并在容器的整个生命周期中使用它们。这个类不能动态的适应运行时的要求或参与消息接收的事务处理。然而它对JMS提供者的要求也最低。它只需要简单的JMS API。

19.2.4.2. DefaultMessageListenerContainer

这个消息侦听器使用的最多。和SimpleMessageListenerContainer一样,这个子类不能动态适应运行时侯的要求。然而,它可以参与事务管理。每个收到的消息都注册到一个XA事务中(如果配置过),这样就可以利用XA事务语义的优势了。这个类在对JMS提供者的低要求和提供包括事务参于等的强大功能上取得了很好的平衡。

19.2.4.3. ServerSessionMessageListenerContainer

这个子类是三者中最强大的。它利用JMS ServerSessionPool SPI允许对JMS session进行动态管理。它也支持事务。使用这种消息侦听器可以获得强大的运行时调优功能,但是对使用到的JMS提供者有很高的要求(ServerSessionPool SPI)。如果不需要运行时的性能调整,请使用DefaultMessageListenerContainerSimpleMessageListenerContainer

19.2.5. 事务管理

Spring提供了一个JmsTransactionManager为单个JMSConnectionFactory管理事务。这将允许JMS应用利用第 9 章 事务管理中描述的Spring的事务管理功能。JmsTransactionManager从指定的ConnectionFactory绑定了一个Connection/Session对到线程上。然而,在Java EE环境中,SingleConnectionFactory将把连接和session放到缓冲池中,所以绑定到线程的实例将依赖越缓冲池的行为。在标准环境下,使用Spring的SingleConnectionFactory将使得和每个事务相关的JMS连接有自己的session。JmsTemplate也可以和JtaTransactionManager以及具有XA能力的JMS ConnectionFactory一起使用来提供分布式交易。

当使用JMS API从一个连接中创建session时,在受管理的和非受管理的事务环境下重用代码会可能会让人迷惑。这是因为JMS API只有一个工厂方法来创建session并且它需要用于事务和模式确认的值。在受管理的环境下,由事务结构环境负责设置这些值,这样在供应商包装的JMS连接中可以忽略这些值。当在一个非管理性的环境中使用JmsTemplate时,你可以通过使用属性SessionTransactedSessionAcknowledgeMode来指定这些值。当配合 JmsTemplate中使用PlatformTransactionManager时,模板将一直被赋予一个事务性JMS的 Session