26.2 Spring JMS的使用
26.2.1 JmsTemplate
JmsTemplate
类是JMS核心包中的中心类。它简化了 JMS 的使用,因为在发送或同步接收消息时它帮我们处理了资源的创建和释放。
使用JmsTemplate
的代码只需要实现规范中定义的回调接口。在JmsTemplate
中通过调用代码让MessageCreator
回调接口用所提供的会话(Session
)创建消息。然而,为了顾及更复杂的 JMS API 应用,回调接口SessionCallback
将 JMS 会话提供给用户,回调接口ProducerCallback
则公开了Session
和MessageProducer
的组合。
JMS API 公开了发送方法的两种类型,一种接受交付模式、优先级和存活时间作为服务质量(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 会话。