26.2 Spring JMS的使用

26.2.1 JmsTemplate

JmsTemplate类是JMS核心包中的中心类。它简化了 JMS 的使用,因为在发送或同步接收消息时它帮我们处理了资源的创建和释放。

使用JmsTemplate的代码只需要实现规范中定义的回调接口。在JmsTemplate中通过调用代码让MessageCreator回调接口用所提供的会话(Session)创建消息。然而,为了顾及更复杂的 JMS API 应用,回调接口SessionCallback将 JMS 会话提供给用户,回调接口ProducerCallback则公开了Session和MessageProducer的组合。

JMSAPI 公开了发送方法的两种类型,一种接受交付模式、优先级和存活时间作为服务质量(QOS)参数,另一种则使用缺省值作为 QOS 参数(无需参数)方式。由于 JmsTemplate 中有很多发送方法,QOS 参数用 bean 属性进行暴露设置,从而避免在一系列发送方法中的重复。同样地,使用setReceiveTimeout属性设置用于同步接收调用的超时值。

一些JMS 提供者通过配置ConnectionFactory,管理方式上允许默认的 QOS值 的设置。MessageProducer的发送方法send(Destination destination, Message message)在那些专有的 JMS 中将会使用不一样的 QOS 默认值。 所以,为了提供对 QOS 值域、的统一管理,JmsTemplate必须通过设置布尔值属性isExplicitQosEnabled为true,使它能够使用自己的QOS值。

为了方便起见,JmsTemplate还暴露了一个基本的请求-回复操作,允许在一个作为操作一部分而被创建的临时队列上,进行消息的发送与等待回复。

配置的JmsTemplate类的实例是线程安全的。这很重要,因为这意味着你可以配置一个JmsTemplate单例,然后安全地将这个共享引用注入给多个协作者。 要清楚,保持对ConnectionFactory引用的JmsTemplate是有状态的,但该状态不是会话状态。

从Spring Framework 4.1开始,JmsMessagingTemplate构建在JmsTemplate之上,并提供与消息抽象层(即org.springframework.messaging.Message)的集成。 这允许你以通用的方式来创建要发送的消息。

26.2.2 Connections

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

当在EJB 内使用 JMS 时,供应商提供 JMS 接口的实现,以至于可以参与声明式事务的管理,提供连接池和会话池。为了使用这个实现,J2EE 容器一般要求你在 EJB 或 servlet 部署描述符中将 JMS 连接工厂声明为 resource-ref。为确保可以在 EJB 内使用JmsTemplate的这些特性,客户端应当确保它能引用其中的ConnectionFactory实现。

缓存消息资源

标准的API涉及创建许多中间对象。要发送消息,将执行以下“API”步骤

ConnectionFactory->Connection->Session->MessageProducer->send 

在ConnectionFactory和Send操作之间,有三个中间对象被创建和销毁。 为了优化资源使用并提高性能,提供了两个ConnectionFactory的实现。

SingleConnectionFactory

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

CachingConnectionFactory

CachingConnectionFactory扩展了SingleConnectionFactory的功能,它添加了会话、消息生产者、消息消费者的缓存。 初始缓存大小设置为1,使用sessionCacheSize属性来增加缓存会话的数量。请注意,实际缓存会话的数量将超过该值,因为会话的缓存是基于确认模式的,因此当设置sessionCacheSize为1时,缓存的会话可能达到4个,因为每个确认模式都会缓存一个。当缓存的时候,消息生产者和消息消费者被缓存在他们自己的会话中同时也考虑到生产者和消费者的唯一属性。消息生产者基于他们的目的地被缓存,消息消费者基于目的地、选择器、非本地传送标识和持久订阅名称(假设创建持久消费者)的组合键被缓存。

26.2.3 Destination 管理

目的地(Destination),像ConnectionFactories一样,是可以在 JNDI 中进行存储和提取的 JMS 管理对象。当配置一个 Spring 应用上下文,可以使用 JNDI 工厂类JndiObjectFactoryBean / <jee:jndi-lookup>将你的对象引用依赖注入到 JMS 目的地。然而,如果在应用中有大量的目的地,或者 JMS 供应商提供了特有的高级目的地管理特性,这个策略常常显得很笨重。高级目的地管理的例子如创建动态目的地或支持目的地的命名层次。JmsTemplate将目的地名称到 JMS 目的地对象的解析委派给一个DestinationResolver接口的实现。DynamicDestinationResolver是JmsTemplate使用的默认实现,并且提供动态目的地解析。同时JndiDestinationResolver作为 JNDI 包含的目的地的服务定位器,并且可选择地退回来使用DynamicDestinationResolver提供的行为。

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

布尔属性PubSubDomain被用来配置JmsTemplate使用什么样的 JMS 域。这个属性的默认值是 false,使用点到点的队列。JmsTemplate使用该属性决定了通过DestinationResolver的实现来解析动态目的地的行为。

你还可以通过属性DefaultDestination配置一个带有默认目的地的JmsTemplate。默认的目的地被使用时,它的发送和接收操作不需要指定一个特定的目的地。

26.2.4 消息监听容器

在EJB 世界里,JMS 消息最常用的功能之一是用于实现消息驱动 Bean(MDB)。Spring 提供了一个方法来创建消息驱动的 POJO(MDP),并且不会把用户绑定在某个 EJB 容器上。(参见第26.4.2节“异步接收 - 消息驱动的 POJO”,详细介绍了 Spring 的 MDP 支持)。从 Spring Framework 4.1开始,端点方法可以简单使用 @JmsListener 注解,参见第26.6节“注释驱动的侦听器端点 “ 更多细节。

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

有两个标准的 JMS 消息监听容器包含在 Spring 中,每一个都有它特殊的功能集。

SimpleMessageListenerContainer

这个消息监听容器是两种标准风格中比较简单的一个,它在启动时创建固定数量的 JMS 会话和消费者,使用标准的 JMS 方法MessageConsumer.setMessageListener()注册监听,并且让 JMS 提供者做监听回调。它不适于动态运行要求或者参与额外管理事务。兼容上,它与标准的 JMS 规范很近,但它通常情况下不兼容 Java EE 的 JMS 限制条件。

虽然SimpleMessageListenerContainer不允许参与外部管理的事务,但它确实支持原生 JMS 事务:只需将sessionTransacted标志切换为 true,或者在命名空间中将acknowledge属性设置为 transacted:监听器抛出的异常将会导致回滚,然后消息被重新传递。或者,考虑使用CLIENT_ACKNOWLEDGE模式,在异常的情况下提供重新传递,但没有使用事务会话,因此在事务协议中不包括任何其他会话操作(例如发送响应消息)。

DefaultMessageListenerContainer

这个消息监听容器用于大部分的案例中。与SimpleMessageListenerContainer相反的是,这个容器适于动态运行要求并且能参与额外管理事务。 在配置JtaTransactionManager的时候,每一个被接收的消息使用 XA 事务注册,因此可能利用 XA 事务语法处理。该监听容器在 JMS 提供者低要求、高级功能(如外部管理事务的参与)以及与 Java EE 环境的兼容性之间取得了良好的平衡。

容器缓存等级可以定制,注意当缓存不可用的时候,每一次消息接收,一个新的连接和新的会话就会被创建。使用高负载的非持久化订阅可能导致消息丢失,在这种情况下,确保使用合适的缓存等级。

当代理挂掉时,此容器也具备可恢复的能力。默认情况下,一个简单的BackOff实现会每5秒重试一次。可以为更细粒度的恢复选项指定自定义的BackOff实现,请参见ExponentialBackOff示例。

与同级的SimpleMessageListenerContainer一样,DefaultMessageListenerContainer支持原生 JMS 事务,并允许自定义确认模式。如果可行的话,强烈建议您使用外部管理的事务:即,如果你可以忍受在 JVM 挂掉的情况下偶尔会重复发送消息。业务逻辑中的自定义重复消息检测步骤可能涵盖这些情况,例如以业务实体的形式存在的检查或协议表检查。这样的安排将比任何其他方式显着更有效:用 XA 事务(通过使用JtaTransactionManager配置你的DefaultMessageListenerContainer)来包裹整个过程,覆盖了 JMS 消息的接收以及消息监听器中业务逻辑的执行(包括数据库操作等)。

26.2.5 事务管理

Spring 提供了一个JmsTransactionManager用于对 JMS ConnectionFactory做事务管理。这将允许 JMS 应用利用 Spring 的事务管理特性。第13章事务管理中所述的 Spring 的托管事务功能。JmsTransactionManager在执行本地资源事务管理时将从指定的ConnectionFactory绑定一个ConnectionFactory/Session这样的配对到线程中。JmsTemplate会自动检测这样的事务资源,并对它们进行相应操作。

在Java EE 环境中,ConnectionFactory会池化连接和会话,这样这些资源将会在整个事务中被有效地重复利用。在一个独立的环境中,使用 Spring 的SingleConnectionFactory时所有的事务将公用一个JMS 连接,但是每个事务将保留自己独立的会话。或者,请考虑使用具体提供者的池适配器,如 ActiveMQ 的PooledConnectionFactory类。

JmsTemplate也利用JtaTransactionManager和支持 XA 的 JMS ConnectionFactory一起来执行分布式事务。请注意,这需要使用 JTA 事务管理器以及正确的 XA 配置的ConnectionFactory!(检查您的 Java EE 服务/ JMS 提供者的文档。)

在使用JMS API 从连接中创建会话时,通过托管和非托管事务环境重用代码可能会令人困惑。 这是因为 JMS API 只有一种工厂方法来创建会话,它需要对事务和确认模式赋值。在托管环境中,设置这些值是环境事务性基础架构的责任,因此供应商对 JMS 连接的包装器将忽略这些值。 在非托管环境中使用JmsTemplate时,可以通过使用属性sessionTransacted和sessionAcknowledgeMode来指定这些值。 当与JmsTemplate一起使用PlatformTransactionManager时,模板将始终被赋予一个事务性 JMS 会话。