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值。

注意

JmsTemplate 类的实例 一经配置便是线程安全 的。 这很重要,因为这意味着你可以配置一个 JmsTemplate 的单例,然后把这个 共享的 引用安全的注入多个协作的对象中。 要清楚一点,JmsTemplate 是有状态的,因为它维护了 ConnectionFactory 的引用,但这个状态时不是会话状态。

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(MDB)。Spring提供了一个方法来创建消息驱动的POJO(MDP),并且不会把用户绑定在某个EJB容器上。(关于Spring的MDP支持的细节请参考标题为 第 19.4.2 节 “异步接收 - 消息驱动的POJO” 的章节。)

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

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

19.2.4.1. SimpleMessageListenerContainer

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

19.2.4.2. DefaultMessageListenerContainer

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

19.2.4.3. ServerSessionMessageListenerContainer

这个监听器容器利用JMS ServerSessionPool SPI动态管理JMS Session。 使用者各种消息监听器可以获得运行时动态调优功能,但是这也要求JMS提供者支持ServerSessionPool SPI。如果不需要运行时性能调整,请使用 DefaultMessageListenerContainerSimpleMessageListenerContainer

19.2.5. 事务管理

Spring提供了 JmsTransactionManager 为单个JMS ConnectionFactory 管理事务。这将允许JMS应用利用 第 9 章 事务管理 中描述的Spring的事务管理功能。JmsTransactionManager 绑定 ConnectionFactory 的一个Connection/Session对到线程上,来提供本地资源事务。JmsTemplate 自动检测到这些事务性资源从而对它们进行操作。

在Java EE环境中,SingleConnectionFactory将把Connection和Session放到缓冲池中,因此这些资源在事务中得到了有效的复用。在独立环境中使用Spring的 SingleConnectionFactory 会存在共享的JMS Connection,但每个事务有自己独立的 Session。另外可以考虑使用供应商特定的池适配器,,如ActiveMQ的 PooledConnectionFactory 类。

JmsTemplate 也可以和 JtaTransactionManager 以及具有XA能力的JMS ConnectionFactory一起使用来提供分布式事务。记住这需要使用JTA事务管理器或合适的可配置的XA ConnectionFactory!(参考你所使用的J2EE服务器/JMS供应商的文档。)

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