使用 Spring JMS
本节介绍如何使用 Spring 的 JMS 组件。
使用 JmsTemplate
JmsTemplate
类是 JMS 核心包中的核心类。它简化了 JMS 的使用,因为它在发送或同步接收消息时处理资源的创建和释放。
使用 JmsTemplate
的代码只需要实现回调接口,这些接口为它们提供了明确定义的高级契约。MessageCreator
回调接口在 JmsTemplate
中调用代码提供的 Session
时创建一个消息。为了允许更复杂地使用 JMS API,SessionCallback
提供 JMS 会话,而 ProducerCallback
公开一个 Session
和 MessageProducer
对。
JMS API 公开了两种类型的发送方法,一种采用传递模式、优先级和生存时间作为服务质量 (QOS) 参数,另一种不采用任何 QOS 参数并使用默认值。由于 JmsTemplate
具有许多发送方法,因此将 QOS 参数设置为 bean 属性以避免发送方法数量重复。类似地,同步接收调用的超时值通过使用 setReceiveTimeout
属性设置。
某些 JMS 提供程序允许通过 ConnectionFactory
的配置以管理方式设置默认 QOS 值。这会产生这样的效果:对 MessageProducer
实例的 send
方法 (send(Destination destination, Message message)
) 的调用会使用与 JMS 规范中指定的不同的 QOS 默认值。为了提供对 QOS 值的一致管理,因此必须通过将布尔属性 isExplicitQosEnabled
设置为 true
来明确启用 JmsTemplate
以使用其自己的 QOS 值。
为方便起见,JmsTemplate
还公开了基本请求-响应操作,该操作允许发送消息并在临时队列上等待答复,该临时队列是作为操作的一部分创建的。
一旦配置,JmsTemplate 类的实例就是线程安全的。这一点很重要,因为它意味着您可以配置 JmsTemplate 的单个实例,然后安全地将此共享引用注入到多个协作者中。明确地说,JmsTemplate 是有状态的,因为它保留对 ConnectionFactory 的引用,但此状态不是会话状态。
|
从 Spring Framework 4.1 开始,JmsMessagingTemplate
建立在 JmsTemplate
之上,并提供与消息传递抽象(即 org.springframework.messaging.Message
)的集成。这使您可以以通用方式创建要发送的消息。
连接
JmsTemplate
需要对 ConnectionFactory
的引用。ConnectionFactory
是 JMS 规范的一部分,用作使用 JMS 的入口点。客户端应用程序使用它作为工厂来创建与 JMS 提供程序的连接,并封装各种配置参数,其中许多是特定于供应商的,例如 SSL 配置选项。
在 EJB 中使用 JMS 时,供应商提供 JMS 接口的实现,以便它们可以参与声明式事务管理并执行连接和会话池。为了使用此实现,Jakarta EE 容器通常要求您在 EJB 或 servlet 部署描述符中将 JMS 连接工厂声明为 resource-ref
。为了确保在 EJB 中使用 JmsTemplate
时使用这些功能,客户端应用程序应确保它引用 ConnectionFactory
的受管实现。
缓存消息传递资源
标准 API 涉及创建许多中间对象。要发送消息,将执行以下“API”遍历
ConnectionFactory->Connection->Session->MessageProducer->send
在 ConnectionFactory
和 Send
操作之间,创建并销毁了三个中间对象。为了优化资源使用并提高性能,Spring 提供了 ConnectionFactory
的两种实现。
使用 SingleConnectionFactory
Spring 提供了 ConnectionFactory
接口的实现 SingleConnectionFactory
,它在所有 createConnection()
调用中返回相同的 Connection
,并忽略对 close()
的调用。这对于测试和独立环境非常有用,以便可以将同一连接用于多个 JmsTemplate
调用,这些调用可能跨越任意数量的事务。SingleConnectionFactory
引用一个标准的 ConnectionFactory
,该 ConnectionFactory
通常来自 JNDI。
使用 CachingConnectionFactory
CachingConnectionFactory
扩展了 SingleConnectionFactory
的功能,并增加了对 Session
、MessageProducer
和 MessageConsumer
实例的缓存。初始缓存大小设置为 1
。您可以使用 sessionCacheSize
属性来增加缓存会话的数量。请注意,实际缓存会话的数量多于该数量,因为会话是根据其确认模式进行缓存的,因此当 sessionCacheSize
设置为 1 时,最多可以有 4 个缓存会话实例(每个确认模式一个)。MessageProducer
和 MessageConsumer
实例在它们自己的会话中缓存,并且在缓存时也会考虑生产者和消费者的唯一属性。MessageProducer 根据其目标进行缓存。MessageConsumer 根据由目标、选择器、noLocal 传递标志和持久订阅名称(如果创建持久消费者)组成的键进行缓存。
临时队列和主题(TemporaryQueue/TemporaryTopic)的 MessageProducer 和 MessageConsumer 永远不会被缓存。不幸的是,WebLogic JMS 碰巧在其常规目标实现上实现了临时队列/主题接口,错误地表明其任何目标都无法缓存。请在 WebLogic 上使用不同的连接池/缓存,或为 WebLogic 目的自定义 |
目标管理
目标(作为 ConnectionFactory
实例)是 JMS 管理的对象,您可以在 JNDI 中存储和检索它们。在配置 Spring 应用程序上下文时,您可以使用 JNDI JndiObjectFactoryBean
工厂类或 <jee:jndi-lookup>
来对对象对 JMS 目标的引用执行依赖注入。但是,如果应用程序中有大量目标或 JMS 提供程序有独特的目标管理高级功能,此策略通常会很麻烦。此类高级目标管理的示例包括创建动态目标或支持目标的分层命名空间。JmsTemplate
将目标名称解析委托给实现了 DestinationResolver
接口的 JMS 目标对象。DynamicDestinationResolver
是 JmsTemplate
使用的默认实现,它支持解析动态目标。还提供了 JndiDestinationResolver
,用作 JNDI 中包含的目标的服务定位器,并可选择回退到 DynamicDestinationResolver
中包含的行为。
通常,JMS 应用程序中使用的目标仅在运行时才知道,因此无法在部署应用程序时以管理方式创建它们。这通常是因为交互系统组件之间存在共享应用程序逻辑,这些组件根据众所周知的命名约定在运行时创建目标。即使创建动态目标不属于 JMS 规范的一部分,大多数供应商也提供了此功能。动态目标使用用户定义的名称创建,这将它们与临时目标区分开来,并且通常不会在 JNDI 中注册。用于创建动态目标的 API 因供应商而异,因为与目标关联的属性是特定于供应商的。但是,供应商有时会做出的一个简单的实现选择是忽略 JMS 规范中的警告,并使用 TopicSession
createTopic(String topicName)
方法或 QueueSession
createQueue(String queueName)
方法使用默认目标属性创建新目标。根据供应商实现,DynamicDestinationResolver
还可以创建物理目标,而不仅仅是解析一个目标。
布尔属性 pubSubDomain
用于配置 JmsTemplate
,使其了解正在使用的 JMS 域。默认情况下,此属性的值为 false,表示要使用点对点域 Queues
。此属性(由 JmsTemplate
使用)通过 DestinationResolver
接口的实现确定动态目标解析的行为。
您还可以通过属性 defaultDestination
使用默认目标配置 JmsTemplate
。默认目标用于不引用特定目标的发送和接收操作。
消息侦听器容器
在 EJB 世界中,JMS 消息最常见的用途之一是驱动消息驱动 Bean (MDB)。Spring 提供了一种创建消息驱动 POJO (MDP) 的解决方案,这种解决方案不会将用户绑定到 EJB 容器。(有关 Spring 的 MDP 支持的详细介绍,请参阅 异步接收:消息驱动 POJO。)从 Spring Framework 4.1 开始,端点方法可以使用 @JmsListener
进行注释——有关更多详细信息,请参阅 基于注释的侦听器端点。
消息侦听器容器用于从 JMS 消息队列接收消息并驱动注入其中的 MessageListener
。侦听器容器负责消息接收的所有线程处理,并分派到侦听器进行处理。消息侦听器容器是 MDP 和消息传递提供程序之间的中介,负责注册以接收消息、参与事务、获取和释放资源、异常转换等。这使您可以编写与接收消息(并可能对其做出响应)相关的(可能很复杂的)业务逻辑,并将样板 JMS 基础设施问题委派给框架。
Spring 中打包了两个标准 JMS 消息侦听器容器,每个容器都有其专门的功能集。
使用 SimpleMessageListenerContainer
此消息侦听器容器是两种标准风格中较简单的。它在启动时创建固定数量的 JMS 会话和使用者,使用标准 JMS MessageConsumer.setMessageListener()
方法注册侦听器,并将其留给 JMS 提供程序来执行侦听器回调。此变体不允许动态适应运行时需求或参与外部管理的事务。在兼容性方面,它非常贴近独立 JMS 规范的精神,但通常与 Jakarta EE 的 JMS 限制不兼容。
虽然 SimpleMessageListenerContainer 不允许参与外部管理的事务,但它确实支持本机 JMS 事务。要启用此功能,您可以将 sessionTransacted 标志切换为 true ,或者在 XML 命名空间中将 acknowledge 属性设置为 transacted 。然后,从侦听器抛出的异常会导致回滚,并重新传递消息。或者,考虑使用 CLIENT_ACKNOWLEDGE 模式,该模式在发生异常时也提供重新传递,但它不使用已进行事务的 Session 实例,因此不会在事务协议中包含任何其他 Session 操作(例如发送响应消息)。
|
默认的 AUTO_ACKNOWLEDGE 模式不提供适当的可靠性保证。当侦听器执行失败(因为提供程序在侦听器调用后自动确认每条消息,而不会向提供程序传播异常)或侦听器容器关闭时(您可以通过设置 acceptMessagesWhileStopping 标志来配置此设置),消息可能会丢失。在需要可靠性时,请务必使用已进行事务的会话(例如,用于可靠的队列处理和持久主题订阅)。
|
使用 DefaultMessageListenerContainer
此消息侦听器容器在大多数情况下使用。与 SimpleMessageListenerContainer
相比,此容器变体允许动态适应运行时需求,并且能够参与外部管理的事务。当使用 JtaTransactionManager
配置时,每条接收到的消息都会使用 XA 事务进行注册。因此,处理可以利用 XA 事务语义。此侦听器容器在对 JMS 提供程序的低要求、高级功能(例如参与外部管理的事务)以及与 Jakarta EE 环境的兼容性之间取得了良好的平衡。
您可以自定义容器的缓存级别。请注意,当未启用缓存时,将为每次消息接收创建新的连接和新的会话。将此与高负载的非持久订阅结合使用可能会导致消息丢失。在这种情况下,请务必使用适当的缓存级别。
当代理服务器关闭时,此容器还具有可恢复功能。默认情况下,一个简单的 BackOff
实现每五秒重试一次。您可以指定一个自定义 BackOff
实现以获得更细粒度的恢复选项。请参阅 ExponentialBackOff
以获取示例。
与它的兄弟 (SimpleMessageListenerContainer ) 一样,DefaultMessageListenerContainer 支持本机 JMS 事务,并允许自定义确认模式。如果对你的场景可行,强烈建议使用这种方法,而不是外部管理的事务——也就是说,如果在 JVM 死机的情况下,你可以忍受偶尔出现重复的消息。业务逻辑中的自定义重复消息检测步骤可以涵盖此类情况——例如,以业务实体存在检查或协议表检查的形式。任何此类安排都比替代方案明显高效:用 XA 事务包装你的整个处理(通过使用 JtaTransactionManager 配置你的 DefaultMessageListenerContainer )以涵盖 JMS 消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。
|
默认的 AUTO_ACKNOWLEDGE 模式不提供适当的可靠性保证。当侦听器执行失败(因为提供程序在侦听器调用后自动确认每条消息,而不会向提供程序传播异常)或侦听器容器关闭时(您可以通过设置 acceptMessagesWhileStopping 标志来配置此设置),消息可能会丢失。在需要可靠性时,请务必使用已进行事务的会话(例如,用于可靠的队列处理和持久主题订阅)。
|
事务管理
Spring 提供了一个 JmsTransactionManager
,它管理单个 JMS ConnectionFactory
的事务。这使 JMS 应用程序能够利用 Spring 的受管理事务特性,如 数据访问章节的事务管理部分 中所述。JmsTransactionManager
执行本地资源事务,将 JMS 连接/会话对从指定的 ConnectionFactory
绑定到线程。JmsTemplate
自动检测此类事务资源,并相应地对其进行操作。
在 Jakarta EE 环境中,ConnectionFactory
池连接和会话实例,因此这些资源在事务中被有效地重用。在独立环境中,使用 Spring 的 SingleConnectionFactory
会产生一个共享的 JMS Connection
,每个事务都有自己独立的 Session
。或者,考虑使用特定于提供程序的池适配器,例如 ActiveMQ 的 PooledConnectionFactory
类。
你还可以将 JmsTemplate
与 JtaTransactionManager
和 XA 兼容的 JMS ConnectionFactory
一起使用来执行分布式事务。请注意,这需要使用 JTA 事务管理器以及正确配置 XA 的 ConnectionFactory。(查看你的 Jakarta EE 服务器或 JMS 提供商的文档。)
当使用 JMS API 从 Connection
创建 Session
时,在受管理和不受管理的事务环境中重用代码可能会令人困惑。这是因为 JMS API 只有一个工厂方法来创建 Session
,并且它需要事务和确认模式的值。在受管理的环境中,设置这些值是环境事务基础设施的责任,因此这些值被供应商对 JMS 连接的包装器忽略。当你在不受管理的环境中使用 JmsTemplate
时,你可以通过使用属性 sessionTransacted
和 sessionAcknowledgeMode
来指定这些值。当你在 JmsTemplate
中使用 PlatformTransactionManager
时,模板始终会得到一个事务 JMS Session
。