JMS(Java消息服务)

Spring提供了一个JMS集成框架,简化了JMS API的使用,其方式与Spring对JDBC API的集成基本相同。

JMS可以大致分为两个功能领域,即消息的生成和消费。JmsTemplate类用于消息生成和同步消息接收。

Spring提供了许多消息侦听器容器,你可以使用它们来创建消息驱动POJO(MDPs)。Spring还提供了一种声明性的方法来创建消息侦听器。

这个org.springframework.jms.core包提供使用JMS的核心功能。它包含JMS模板类,这些类通过处理资源的创建和释放来简化JMS的使用,就像JdbcTemplate为JDBC做的那样。Spring模板类的共同设计原则是提供helper方法来执行常见操作,并且为了更复杂的使用,将处理任务的本质委托给用户实现的回调接口。JMS模板遵循相同的设计。这些类为发送消息、同步消费消息以及向用户公开JMS会话和消息生产者提供了各种方便的方法。

org.springframework.jms.support包提供JMSException转换功能。将选中的JMSException层次结构转换为未检查异常的镜像层次结构。如果javax.jms.JMSException存在时,此异常被包装在未检查的UncategorizedJmsException中。

org.springframework.jms.support.converter提供了一个MessageConverter抽象,用于在Java对象和JMS消息之间进行转换。

org.springframework.jms.support.destination提供管理JMS目的地的各种策略,例如为存储在JNDI中的目的地提供服务定位器。

org.springframework.jms.annotation包通过使用@JmsListener提供必要的基础设施来支持注释驱动的侦听器端点。

org.springframework.jms.config提供了jms命名空间的解析器实现,以及配置侦听器容器和创建侦听器端点的java配置支持。

最后org.springframework.jms.connection包提供了适合在独立应用程序中使用的ConnectionFactory的实现。它还包含Spring的PlatformTransactionManager for JMS(巧妙地命名为JmsTransactionManager)的实现。这允许将JMS作为事务资源无缝集成到Spring的事务管理机制中。

从Spring Framework 5开始,Spring的JMS包完全支持JMS 2.0,并要求在运行时提供JMS 2.0 API。我们建议使用JMS 2.0兼容地提供程序。如果你碰巧在你的系统中使用了旧的消息代理,你可以尝试升级到JMS 2.0兼容的驱动程序来生成现有的代理。或者,你也可以尝试针对基于JMS1.1的驱动程序运行,只需将JMS 2.0 API放在类路径上,但只对驱动程序使用JMS1.1兼容的API。默认情况下,Spring的JMS支持遵循JMS1.1约定,因此通过相应的配置,它确实支持这样的场景。但是,请仅考虑过渡场景。

一、使用JMS

JmsTemplate

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

使用JmsTemplate的代码只需要实现回调接口,从而为它们提供一个明确定义的高级契约。当给定由JmsTemplate中的调用代码提供的会话时,MessageCreator回调接口将创建一条消息。为了允许更复杂地使用JMS API,SessionCallback提供JMS会话,ProducerCallback公开会话和MessageProducer对。

JMS API公开了两种类型的发送方法,一种将传递模式、优先级和生存时间作为服务质量(QOS)参数,另一种不接受QOS参数并使用默认值。由于JmsTemplate有许多发送方法,所以设置QOS参数已经作为bean属性公开,以避免发送方法数量上的重复。类似地,同步接收调用的超时值是通过使用setReceiveTimeout属性设置的。

一些JMS提供程序允许通过ConnectionFactory的配置以管理方式设置默认的QOS值。这会导致对MessageProducer实例的send方法(send(Destination Destination,Message Message))的调用使用与JMS规范中指定的不同的QOS默认值。因此,为了提供对QOS值的一致管理,必须通过将布尔属性isExplicitQosEnabled设置为true来具体启用JMST模板以使用其自己的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接口的实现,以便它们能够参与声明性事务管理并执行连接和会话的池化。为了使用这个实现,Java EE容器通常要求你在EJB或servlet部署描述符中声明JMS连接工厂作为资源引用。为了确保这些特性与EJB内的JmsTemplate一起使用,客户机应用程序应该确保它引用ConnectionFactory的托管实现。

缓存消息资源

标准API涉及到创建许多中间对象。要发送消息,请执行以下“API”检查:

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

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

SingleConnectionFactory

Spring提供了ConnectionFactory接口的实现SingleConnectionFactory,它在所有createConnection()调用中返回相同的连接,并忽略对close()的调用。这对于测试和独立环境非常有用,以便同一连接可以用于可能跨越任意数量事务的多个JmsTemplate调用。SingleConnectionFactory引用通常来自JNDI的标准ConnectionFactory。

CachingConnectionFactory

CachingConnectionFactory扩展了SingleConnectionFactory的功能,并添加了Session、MessageProducer和MessageConsumer实例的缓存。初始缓存大小设置为1。可以使用sessionCacheSize属性来增加缓存的会话数。请注意,实际缓存的会话数大于该数量,因为会话是根据其确认模式进行缓存的,因此当sessionCacheSize设置为1时,最多可以有四个缓存会话实例(每个确认模式一个)。MessageProducer和MessageConsumer实例缓存在它们所属的会话中,并且在缓存时还考虑生产者和使用者的独特属性。MessageProducers根据其目的地进行缓存。MessageConsumers是根据由destination、selector、noLocal delivery标志和持久订阅名称(如果创建持久使用者)组成的键进行缓存的。

目的地管理

目的地,作为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(字符串topicName)或QueueSession createQueue(String queueName)方法创建具有默认目的地属性的新目标。根据供应商实现的不同,DynamicDestinationResolver还可以创建一个物理目标,而不是只解析一个。

boolean属性pubSubDomain用于配置JmsTemplate,以了解正在使用的JMS域。默认情况下,此属性的值为false,表示将使用点到点域Queues。此属性(由JmsTemplate使用)通过DestinationResolver接口的实现确定动态目标解析的行为。还可以通过属性defaultDestination配置带有默认目的地的JmsTemplate。默认目的地是发送和接收操作不引用特定目标。

消息监听容器

JMS消息在EJB世界中最常见的用途之一是驱动消息驱动bean(MDBs)。Spring提供了一种创建消息驱动POJO(MDPs)的解决方案,这种方法不会将用户绑定到EJB容器。(请参阅Asynchronous Receiving:Message Driven POJO了解Spring的MDP支持的详细内容。)自Spring Framework 4.1以来,端点方法可以用@JmsListener 。

消息侦听器容器用于从JMS消息队列接收消息,并驱动注入其中的MessageListener。侦听器容器负责消息接收的所有线程化,并将其分派到侦听器中进行处理。消息侦听器容器是MDP和消息传递提供者之间的中介,负责注册以接收消息、参与事务、资源获取和释放、异常转换等。这使你可以编写与接收消息相关联的(可能复杂的)业务逻辑(并可能对其作出响应),并将JMS基础设施的样板问题委托给框架。

Spring提供了两个标准的JMS消息侦听器容器,每个容器都有其专门的特性集。

  • SimpleMessageListenerContainer
  • DefaultMessageListenerContainer
使用SimpleMessageListenerContainer

它在启动时创建固定数量的JMS会话和使用者,使用标准JMS注册侦听器MessageConsumer.setMessageListener()方法,并将其留给JMS提供程序来执行侦听器回调。此变量不允许动态适应运行时要求或参与外部管理的事务。兼容性方面,它非常接近独立JMS规范的精神,但通常不兼容Java EE的JMS限制。

虽然SimpleMessageListenerContainer不允许参与外部托管事务,但它支持本机JMS事务。要启用此功能,可以将sessionTransactivated标志切换为true,或者在XML名称空间中将acknowledge属性设置为transactied。从侦听器抛出的异常将导致回滚,并重新传递消息。或者,考虑使用客户端确认模式,该模式在异常情况下也提供重新传递,但不使用事务化会话实例,因此在事务协议中不包括任何其他会话操作(如发送响应消息)。

默认的AUTO_ACKNOWLEDGE(自动确认)模式不能提供正确的可靠性保证。当侦听器执行失败时(因为提供程序在侦听器调用后自动确认每个消息,没有要传播到提供程序的异常)或侦听器容器关闭时(你可以通过设置acceptMessagesWhileStopping标志来配置这一点),消息可能会丢失。确保在可靠性需要时使用事务性会话(例如,用于可靠的队列处理和持久的主题订阅)。

使用DefaultMessageListenerContainer

在大多数情况下都使用此消息侦听器容器。与SimpleMessageListenerContainer不同,此容器变量允许动态适应运行时需求,并能够参与外部管理的事务。使用JtaTransactionManager配置时,每个接收到的消息都注册到XA事务中。因此,处理可以利用XA事务语义。这个侦听器容器在JMS提供程序的低要求、高级功能(例如参与外部管理的事务)和与javaee环境的兼容性之间取得了很好的平衡。

你可以自定义容器的缓存级别。请注意,当没有启用缓存时,将为每个消息接收创建一个新连接和一个新会话。将其与高负载的非持久订阅结合使用可能会导致消息丢失。在这种情况下,一定要使用适当的缓存级别。

当代理关闭时,这个容器还具有可恢复的功能。默认情况下,简单的退避实现每五秒钟重试一次。你可以为更细粒度的恢复选项指定自定义退避实现。

与它的同级(SimpleMessageListenerContainer)一样,DefaultMessageListenerContainer支持本机JMS事务,并允许自定义确认模式。如果对你的场景可行,那么强烈建议在外部管理事务中使用这种方法,也就是说,如果你可以在JVM死亡的情况下偶尔处理重复的消息,那么就强烈建议这样做。业务逻辑中的自定义重复消息检测步骤可以覆盖这样的情况 - ,例如,以业务实体存在性检查或协议表检查的形式。任何这样的安排都比其他方法更有效:用XA事务包装整个处理过程(通过使用JtaTransactionManager配置DefaultMessageListenerContainer)来覆盖JMS消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。

默认的AUTO_ACKNOWLEDGE(自动确认)模式不能提供正确的可靠性保证。当侦听器执行失败时(因为提供程序在侦听器调用后自动确认每个消息,没有要传播到提供程序的异常)或侦听器容器关闭时(你可以通过设置acceptMessagesWhileStopping标志来配置这一点),消息可能会丢失。确保在可靠性需要时使用事务性会话(例如,用于可靠的队列处理和持久的主题订阅)。

事务管理器

Spring提供了一个JmsTransactionManager来管理单个JMS ConnectionFactory的事务。这使得JMS应用程序可以利用Spring的托管事务特性。JmsTransactionManager执行本地资源事务,将JMS连接/会话对从指定的ConnectionFactory绑定到线程。JmsTemplate自动检测此类事务性资源,并相应地对其进行操作。

在Java EE环境中,ConnectionFactory将连接和会话实例集中在一起,因此这些资源可以跨事务高效地重用。在独立环境中,使用Spring的SingleConnectionFactory将导致共享JMS连接,每个事务都有自己独立的会话。或者,考虑使用特定于提供者的池适配器,例如ActiveMQ的PooledConnectionFactory类。

还可以将JmsTemplate与JtaTransactionManager和支持XA的JMS ConnectionFactory一起使用来执行分布式事务。注意,这需要使用JTA事务管理器以及正确配置XA的ConnectionFactory。

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

二、发送消息

JmsTemplate包含许多用于发送消息的方便方法。Send方法通过使用javax.jms.Destination对象和其他对象通过在JNDI查找中使用字符串指定目标。不带destination参数的send方法使用默认目标。

以下示例使用MessageCreator回调从提供的会话对象创建文本消息:

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;

import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;

public class JmsQueueSender {

    private JmsTemplate jmsTemplate;
    private Queue queue;

    public void setConnectionFactory(ConnectionFactory cf) {
        this.jmsTemplate = new JmsTemplate(cf);
    }

    public void setQueue(Queue queue) {
        this.queue = queue;
    }

    public void simpleSend() {
        this.jmsTemplate.send(this.queue, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                return session.createTextMessage("hello queue world");
            }
        });
    }
}

在前面的示例中,JmsTemplate是通过传递对ConnectionFactory的引用来构造的。作为一种替代方案,作为一种替代方案,我们提供了一个零参数构造函数和connectionFactory,并可用于以JavaBean样式(使用BeanFactory或普通Java代码)构造实例。或者,考虑从Spring的JmsGatewaySupport便利基类派生,该基类为JMS配置提供了预构建的bean属性。

send(String destinationName,MessageCreator creator)方法允许你使用目的地的字符串名称发送消息。如果这些名称是在JNDI中注册的,那么应该将模板的destinationResolver属性设置为jndedistinationresolver的实例。

如果你创建了JmsTemplate并指定了一个默认目的地,那么send(MessageCreator c)会向该目的地发送一条消息。

使用消息转换器

为了方便域模型对象的发送,JmsTemplate有各种发送方法,这些方法将Java对象作为消息数据内容的参数。JmsTemplate中的重载方法convertAndSend()和receiveAndConvert()方法将转换过程委托给MessageConverter接口的实例。这个接口定义了一个简单的契约,用于在Java对象和JMS消息之间进行转换。默认实现(SimpleMessageConverter)支持字符串和TextMessage、byte[]和BytesMessage之间的转换,以及java.util.Map还有地图信息。通过使用转换器,你和你的应用程序代码可以关注通过JMS发送或接收的业务对象,而不必关心如何将其表示为JMS消息的细节。

沙盒目前包括一个MapMessageConverter,它使用反射在JavaBean和MapMessage之间进行转换。你可以自己实现的其他流行实现选择是使用现有的XML编组包(如JAXB、Castor或XStream)来创建表示对象的TextMessage的转换器。

为了适应消息的属性、头和正文的设置,而这些属性、头和正文不能被一般性地封装在转换器类中,MessagePostProcessor接口允许你在消息被转换后发送之前访问它。下面的示例演示如何修改消息头和java.util.Map转换为邮件:

public void sendWithConversion() {
    Map map = new HashMap();
    map.put("Name", "Mark");
    map.put("Age", new Integer(47));
    jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws JMSException {
            message.setIntProperty("AccountID", 1234);
            message.setJMSCorrelationID("123-00001");
            return message;
        }
    });
}

这将导致以下形式的消息:

MapMessage={
    Header={
        ... standard headers ...
        CorrelationID={123-00001}
    }
    Properties={
        AccountID={Integer:1234}
    }
    Fields={
        Name={String:Mark}
        Age={Integer:47}
    }
}
使用SessionCallback 和 ProducerCallback

虽然send操作涉及许多常见的使用场景,但有时你可能希望对JMS会话或MessageProducer执行多个操作。SessionCallback和ProducerCallback分别公开JMS Session and Session / MessageProducer。JmsTemplate上的execute()方法执行这些回调方法。

三、接收消息

同步接收

虽然JMS通常与异步处理关联,但你也可以同步使用消息。重载的receive(..)方法提供了此功能。在同步接收期间,调用线程将阻塞,直到消息可用为止。这可能是一个危险的操作,因为调用线程可能被无限期地阻塞。receiveTimeout属性指定接收方在放弃等待消息之前应等待多长时间。

异步接收——消息驱动的POJO

Spring还通过使用@JmsListener注释来支持带注释的侦听器端点,并提供了一个开放的基础设施来以编程方式注册端点。到目前为止,这是设置异步接收器最方便的方法。

与EJB世界中的消息驱动Bean(MDB)类似,消息驱动POJO(MDP)充当JMS消息的接收器。MDP的一个限制是它必须实现javax.jms.MessageListener接口。注意,如果你的POJO在多个线程上接收消息,那么确保你的实现是线程安全的是很重要的。

以下示例显示了MDP的简单实现:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            try {
                System.out.println(((TextMessage) message).getText());
            }
            catch (JMSException ex) {
                throw new RuntimeException(ex);
            }
        }
        else {
            throw new IllegalArgumentException("Message must be of type TextMessage");
        }
    }
}

一旦实现了MessageListener,就应该创建一个消息侦听器容器了。

以下示例显示如何定义和配置Spring附带的消息侦听器容器之一(在本例中为DefaultMessageListenerContainer):

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener"/>

<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
</bean>
使用SessionAwareMessageListener接口

SessionAwareMessageListener接口是一个特定于Spring的接口,它提供了与JMS MessageListener类似的契约,但也为消息处理方法提供了对从中接收消息的JMS会话的访问。下表显示了SessionAwareMessageListener接口的定义:

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

    void onMessage(Message message, Session session) throws JMSException;
}

如果你希望MDPs能够响应任何接收到的消息(通过使用onMessage(Message,Session)方法中提供的会话),你可以选择让MDPs实现此接口(优先于标准JMS MessageListener接口)。

Spring附带的所有消息侦听器容器实现都支持实现MessageListener或SessionAwareMessageListener接口的MDPs,实现SessionAwareMessageListener的类附带一个警告,即它们随后通过接口绑定到Spring。

注意,SessionAwareMessageListener接口的onMessage(..)方法抛出JMSException。与标准JMS MessageListener接口不同,在使用SessionAwareMessageListener接口时,客户端代码负责处理任何抛出的异常。

使用MessageListenerAdapter

MessageListenerAdapter类是Spring异步消息传递支持的最后一个组件。简而言之,它允许你将几乎任何类公开为MDP(尽管有一些限制)。

考虑以下接口定义:

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Map message);

    void handleMessage(byte[] message);

    void handleMessage(Serializable message);
}

请注意,尽管接口既不扩展MessageListener也不扩展SessionAwareMessageListener接口,但你仍然可以通过使用MessageListenerAdapter类将其用作MDP。还要注意各种消息处理方法是如何根据它们可以接收和处理的各种消息类型的内容进行强类型化的。

现在考虑MessageDelegate接口的以下实现:

public class DefaultMessageDelegate implements MessageDelegate {
    // implementation elided for clarity...
}

特别要注意的是,前面的MessageDelegate接口实现(DefaultMessageDelegate类)根本没有JMS依赖关系。它确实是一个POJO,我们可以通过以下配置将其制成MDP:

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultMessageDelegate"/>
    </constructor-arg>
</bean>

<!-- and this is the message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
</bean>

下一个示例显示了另一个只能处理接收JMS TextMessage消息的MDP。请注意消息处理方法实际上是如何调用receive的(MessageListenerAdapter中的消息处理方法的名称默认为handleMessage),但它是可配置的(你将在本节后面看到)。还要注意receive(..)方法是如何被强类型化的,以便只接收和响应JMS TextMessage消息。下表显示了TextMessageDelegate接口的定义:

public interface TextMessageDelegate {

    void receive(TextMessage message);
}

下表显示了一个实现TextMessageDelegate接口的类:

public class DefaultTextMessageDelegate implements TextMessageDelegate {
    // implementation elided for clarity...
}

MessageListenerAdapter的配置如下:

<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultTextMessageDelegate"/>
    </constructor-arg>
    <property name="defaultListenerMethod" value="receive"/>
    <!-- we don't want automatic message context extraction -->
    <property name="messageConverter">
        <null/>
    </property>
</bean>

请注意,如果messageListener接收到TextMessage以外类型的JMS消息,则会抛出一个IllegalStateException(并随后吞并)。MessageListenerAdapter类的另一个功能是,如果处理程序方法返回非空值,则可以自动发回响应消息。考虑以下接口和类:

public interface ResponsiveTextMessageDelegate {

    // notice the return type...
    String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

如果将DefaultResponsiveTextMessageDelegate与MessageListenerAdapter一起使用,则从执行“receive(..)”方法返回的任何非空值(在默认配置中)将转换为TextMessage。然后,生成的TextMessage被发送到原始消息的JMS Reply-to属性中定义的目标(如果存在)或MessageListenerAdapter上设置的默认目标(如果已配置)。如果找不到目标,则抛出InvalidDestinationException(请注意,此异常不会被吞并并传播到调用堆栈中)。

处理事务中的消息

在事务中调用消息侦听器只需要重新配置侦听器容器。

可以通过侦听器容器定义上的sessionTransacted标志激活本地资源事务。 然后,每个消息侦听器调用都在一个活动的JMS事务中运行,如果侦听器执行失败,消息接收将回滚。发送响应消息(通过SessionAwareMessageListener)是同一本地事务的一部分,但任何其他资源操作(如数据库访问)都是独立操作的。这通常需要侦听器实现中的重复消息检测,以覆盖数据库处理已提交但消息处理未能提交的情况。

 虑下面的bean定义:

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
    <property name="sessionTransacted" value="true"/>
</bean>

要参与外部管理的事务,你需要配置事务管理器并使用支持外部管理事务的侦听器容器(通常是DefaultMessageListenerContainer)。

要为XA事务参与配置消息侦听器容器,你需要配置JtaTransactionManager(默认情况下,它委托给javaee服务器的事务子系统)。请注意,底层JMS ConnectionFactory需要具有XA功能,并在JTA事务协调器中正确注册。(检查你的Java EE服务器的JNDI资源配置)这使得消息接收和(例如)数据库访问成为同一事务的一部分(使用统一的提交语义,以XA事务日志开销为代价)。

下面的bean定义创建了一个事务管理器:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

然后我们需要将它添加到我们先前的容器配置中。容器负责剩下的。下面的示例演示如何执行此操作:

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
    <property name="transactionManager" ref="transactionManager"/> 
</bean>

四、支持JCA Message Endpoints

从2.5版开始,Spring还提供了对基于JCA的MessageListener容器的支持。JmsMessageEndpointManager尝试从提供程序的ResourceAdapter类名自动确定ActivationSpec类名。因此,通常可以提供Spring的通用JmsActivationSpecConfig,如下例所示:

<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
    <property name="resourceAdapter" ref="resourceAdapter"/>
    <property name="activationSpecConfig">
        <bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
            <property name="destinationName" value="myQueue"/>
        </bean>
    </property>
    <property name="messageListener" ref="myMessageListener"/>
</bean>

或者,可以使用给定的ActivationSpec对象设置JmsMessageEndpointManager。ActivationSpec对象也可以来自JNDI查找(使用<jee:jndi-lookup>). 下面的示例演示如何执行此操作:

<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
    <property name="resourceAdapter" ref="resourceAdapter"/>
    <property name="activationSpec">
        <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
            <property name="destination" value="myQueue"/>
            <property name="destinationType" value="javax.jms.Queue"/>
        </bean>
    </property>
    <property name="messageListener" ref="myMessageListener"/>
</bean>

使用Spring的ResourceAdapterFactoryBean,可以在本地配置目标ResourceAdapter,如下例所示:

<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
    <property name="resourceAdapter">
        <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
            <property name="serverUrl" value="tcp://localhost:61616"/>
        </bean>
    </property>
    <property name="workManager">
        <bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
    </property>
</bean>

指定的WorkManager还可以指向特定于环境的线程池 - - 通常通过SimpleTaskManager实例的asyncTaskExecutor属性。如果碰巧使用多个适配器,请考虑为所有ResourceAdapter实例定义一个共享线程池。

在某些环境中(如weblogic9或更高版本),你可以从JNDI获取整个ResourceAdapter对象(通过使用<jee:jndi查找>). 然后,基于Spring的消息侦听器可以与服务器托管的ResourceAdapter交互,后者也使用服务器的内置WorkManager。

Spring还提供了一个不绑定到JMS的通用JCA消息端点管理器:org.springframework.jca.endpoint.GenericMessageEndpointManager. 此组件允许使用任何消息侦听器类型(例如CCI MessageListener)和任何特定于提供者的ActivationSpec对象。

 

五、注解驱动的Listener Endpoints

异步接收消息的最简单方法是使用带注释的侦听器端点基础设施。简而言之,它允许你将托管bean的方法公开为JMS侦听器端点。下面的示例演示如何使用它:

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(String data) { ... }
}

上一个示例的思想是,只要javax.jms.DestinationmyDestination,则相应地调用processOrder方法(在本例中,使用JMS消息的内容,类似于MessageListenerAdapter提供的内容)。

带注释的端点基础设施通过使用JmsListenerContainerFactory在幕后为每个带注释的方法创建一个消息侦听器容器。这样的容器不是针对应用程序上下文注册的,但是可以通过使用JmsListenerEndpointRegistry bean轻松定位以便于管理。

@JmsListener是java8上的一个可重复注释,因此你可以通过添加额外的@JmsListener声明将多个JMS目的地与同一方法关联起来。

启用Listener Endpoint注解

要启用对@JmsListener注释的支持,可以将@EnableJms添加到@Configuration类中,如下例所示:

@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setDestinationResolver(destinationResolver());
        factory.setSessionTransacted(true);
        factory.setConcurrency("3-10");
        return factory;
    }
}

默认情况下,查找名为jmsListenerContainerFactory的bean作为工厂用于创建消息侦听器容器的源。在这种情况下(忽略JMS基础设施设置),你可以调用processOrder方法,核心轮询大小为三个线程,最大池大小为十个线程。

你可以自定义侦听器容器工厂以用于每个注释,也可以通过实现JmsListenerConfigurer接口来配置显式默认值。仅当至少有一个端点注册而没有特定容器工厂时,才需要默认值

如果你喜欢XML配置,可以使用<jms:annotation-driven>元素,如下例所示:

<jms:annotation-driven/>

<bean id="jmsListenerContainerFactory"
        class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destinationResolver" ref="destinationResolver"/>
    <property name="sessionTransacted" value="true"/>
    <property name="concurrency" value="3-10"/>
</bean>
编程式 Endpoint Registration

JmsListenerEndpoint提供了JMS端点的模型,并负责为该模型配置容器。除了JmsListener注释检测到的端点之外,该基础结构还允许你以编程方式配置端点。下面的示例演示如何执行此操作:

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
        endpoint.setId("myJmsEndpoint");
        endpoint.setDestination("anotherQueue");
        endpoint.setMessageListener(message -> {
            // processing
        });
        registrar.registerEndpoint(endpoint);
    }
}

在前面的示例中,我们使用了SimpleJmsListenerEndpoint,它提供了要调用的实际MessageListener。但是,你也可以构建自己的端点变量来描述自定义调用机制。

请注意,你可以完全跳过@JmsListener的使用,只通过JmsListenerConfigurer以编程方式注册端点。

带注释的Endpoint方法签名

到目前为止,我们已经在端点中注入了一个简单的字符串,但是它实际上可以有一个非常灵活的方法签名。在下面的示例中,我们重写它以使用自定义标头注入订单:

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(Order order, @Header("order_type") String orderType) {
        ...
    }
}

可以在JMS侦听器端点中注入的主要元素如下:

  • 原始的javax.jms.Message或其任何子类(前提是它与传入消息类型匹配)。
  • 这个javax.jms.Session对本机JMS API的可选访问(例如,用于发送自定义回复)。
  • 这个org.springframework.messaging.Message,表示传入的JMS消息。请注意,此消息包含自定义和标准标头(由JmsHeaders定义)。
  • @Header注解的方法参数,用于提取特定的头值,包括标准的JMS头。
  • 一个@Headers带注释的参数,它必须可以赋值给java.util.Map用于访问所有头。
  • 不受支持的类型(消息或会话)之一的非注释元素被视为有效负载。你可以通过用@Payload注释参数来实现这一点。还可以通过添加额外的@Valid来打开验证。

注入Spring消息抽象的能力对于从特定于传输的消息中存储的所有信息而不依赖于传输特定的API尤其有用。下面的示例演示如何执行此操作:

@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }

方法参数的处理由DefaultMessageHandlerMethodFactory提供,你可以进一步自定义它以支持其他方法参数。你也可以在那里自定义转换和验证支持。

例如,如果我们希望在处理订单之前确保订单有效,可以使用@valid对有效负载进行注释,并配置必要的验证器,如下例所示:

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setValidator(myValidator());
        return factory;
    }
}
响应管理

MessageListenerAdapter中现有的支持已经允许你的方法具有非空返回类型。在这种情况下,调用的结果被封装在javax.jms.Message,在原始消息的JMSReplyTo头中指定的目标或侦听器上配置的默认目标中发送。现在可以通过使用消息传递抽象的@SendTo注释来设置默认目的地。

设processOrder方法现在应该返回OrderStatus,我们可以编写它来自动发送响应,如下例所示:

@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
    // order processing
    return status;
}

如果有几个@JmsListener注释的方法,你可以将@SendTo注释放在类级别,以共享默认的应答目的地。

如果需要以独立于传输的方式设置其他头,则可以返回一条消息,方法如下:

@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
    // order processing
    return MessageBuilder
            .withPayload(status)
            .setHeader("code", 1234)
            .build();
}

如果需要在运行时计算响应目标,可以将响应封装在JmsResponse实例中,该实例还提供了在运行时使用的目标。我们可以重写前面的示例,如下所示:

@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
    // order processing
    Message<OrderStatus> response = MessageBuilder
            .withPayload(status)
            .setHeader("code", 1234)
            .build();
    return JmsResponse.forQueue(response, "status");
}

最后,如果需要为响应指定一些QoS值,例如优先级或生存时间,可以相应地配置JmsListenerContainerFactory,如下例所示:

@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        QosSettings replyQosSettings = new QosSettings();
        replyQosSettings.setPriority(2);
        replyQosSettings.setTimeToLive(10000);
        factory.setReplyQosSettings(replyQosSettings);
        return factory;
    }
}

六、JMS Namespace支持

Spring为简化JMS配置提供了一个XML命名空间。要使用JMS名称空间元素,需要引用JMS模式,如下例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jms="http://www.springframework.org/schema/jms" 
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/jms https://www.springframework.org/schema/jms/spring-jms.xsd">

    <!-- bean definitions here -->

</beans>

命名空间由三个顶级元素组成:<annotation-driven/>、<listener-container/>和<jca-listener-container/>。<annotation-driven/>允许使用注释驱动的侦听器端点。<listener-container/>和<jca-listener-container/>定义共享侦听器容器配置,并且可以包含<listener/>子元素。以下示例显示了两个侦听器的基本配置:

<jms:listener-container>

    <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>

    <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>

</jms:listener-container>

前面的示例相当于创建两个不同的侦听器容器bean定义和两个不同的MessageListenerAdapter bean定义,如使用MessageListenerAdapter中所示。除了上例中显示的属性外,listener元素还可以包含几个可选属性。下表描述了所有可用属性:

JMS <listener>元素的属性

  • id:器容器的bean名称。如果没有指定,则会自动生成一个bean名称。
  • destination:监听器的目标名称,必须的,通过DestinationResolver策略解析。
  • ref:处理程序对象的bean名称。必须的。
  • method:要调用的处理程序方法的名称。如果ref属性指向MessageListener或Spring SessionAwareMessageListener,则可以忽略此属性。
  • response-destination:要向其发送响应消息的默认响应目标的名称。这适用于不包含JMSReplyTo字段的请求消息。此目标的类型由侦听器容器的响应目标类型属性确定。注意,这只适用于具有返回值的侦听器方法,对于该方法,每个结果对象都转换为响应消息。
  • subscription:持久订阅的名称(如果有)。
  • selector:此侦听器的可选消息选择器。
  • concurrency:要为此侦听器启动的并发会话或使用者数。该值可以是表示最大值的简单数字(例如,5),也可以是表示下限和上限的范围(例如,3-5)。请注意,指定的最小值只是一个提示,可能在运行时被忽略。默认值是容器提供的值。

<listener-container/>元素还接受几个可选属性。这允许定制各种策略(例如,taskExecutor和destinationResolver)以及基本的JMS设置和资源引用。通过使用这些属性,你可以定义高度定制的侦听器容器,同时仍然可以受益于命名空间的便利性。

通过指定要通过factory id属性公开的bean的id,可以自动将此类设置公开为JmsListenerContainerFactory,如下例所示:

<jms:listener-container connection-factory="myConnectionFactory"
        task-executor="myTaskExecutor"
        destination-resolver="myDestinationResolver"
        transaction-manager="myTransactionManager"
        concurrency="10">

    <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>

    <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>

</jms:listener-container>

JMS <listener-container>元素的属性

  • container-type:此侦听器容器的类型。可用的选项有default、simple、default102或simple102(默认选项为default)。
  • container-class:作为完全限定类名的自定义侦听器容器实现类。根据container-type属性,默认值是Spring的标准DefaultMessageListenerContainer或SimpleMessageListenerContainer。
  • factory-id:公开此元素定义为具有指定id的JmsListenerContainerFactory的设置,以便可以与其他终结点一起重用。
  • connection-factory:对JMS ConnectionFactory bean的引用(缺省bean名称是ConnectionFactory)。
  • task-executor:对JMS侦听器调用器的Spring TaskExecutor的引用。
  • destination-resolver:对解析JMS目标实例的DestinationResolver策略的引用。
  • message-converter:对用于将JMS消息转换为侦听器方法参数的MessageConverter策略的引用。默认值是SimpleMessageConverter。
  • error-handler:对ErrorHandler策略的引用,用于处理在执行MessageListener期间可能发生的任何未捕获的异常。
  • destination-type:侦听器的JMS目标类型:queue、topic、durableTopic、sharedTopic或sharedDurableTopic。这可能会启用容器的pubSubDomain、subscriptionDurable和subscriptionShared属性。默认值为queue(这将禁用这三个属性)。
  • response-destination-type:响应的JMS目标类型:队列或主题。默认值是destination-type属性的值。
  • client-id:此侦听器容器的JMS客户机标识。使用持久订阅时必须指定它。
  • cache:JMS资源的缓存级别:none、connection、session、consumer或auto。默认情况下(auto),缓存级别实际上是使用者,除非指定了外部事务管理器 - - ,在这种情况下,有效的默认值将是none(假设Java EE的事务管理,其中给定的ConnectionFactory是一个支持XA的池)。
  • acknowledge:本机JMS确认模式:auto、client、dups-ok或transactied。值transactied激活本地事务处理会话。另一种方法是,您可以指定事务管理器属性,如表后面所述。默认为自动。
  • transaction-manager:对外部平台TransactionManager(通常是基于XA的事务协调器,如Spring的JtaTransactionManager)的引用。如果未指定,则使用本机确认。
  • concurrency:每个侦听器启动的并发会话或使用者数。它可以是表示最大值的简单数字(例如,5),也可以是表示下限和上限的范围(例如,3-5)。请注意,指定的最小值只是一个提示,可能在运行时被忽略。默认值为1。对于主题侦听器或队列排序很重要,您应该将并发性限制为1。考虑将其提高到一般队列。
  • prefetch:要加载到单个会话中的最大消息数。请注意,增加这个数字可能会导致并发用户的饥饿。
  • receive-timeout:用于接收调用的超时(毫秒)。默认值为1000(1秒)。-1表示没有超时。
  • back-off:指定用于计算恢复尝试之间的间隔的BackOff实例。如果BackOffExecution实现返回BackOffExecution#STOP,则侦听器容器不会进一步尝试恢复。设置此属性时,将忽略恢复间隔值。默认值是间隔为5000毫秒(即5秒)的FixedBackOff。
  • recovery-interval:指定恢复尝试之间的间隔(毫秒)。它提供了一种方便的方法来创建具有指定间隔的FixedBackOff。有关更多恢复选项,请考虑指定一个BackOff实例。默认值为5000毫秒(即5秒)。
  • phase:此容器应在其中启动和停止的生命周期阶段。值越低,此容器开始的时间越早,停止的时间越晚。默认值是Integer.MAX_VALUE,这意味着容器开始得越晚,停止得越快越好。

配置具有jms模式支持的基于JCA的侦听器容器非常相似,如下例所示:

<jms:jca-listener-container resource-adapter="myResourceAdapter"
        destination-resolver="myDestinationResolver"
        transaction-manager="myTransactionManager"
        concurrency="10">

    <jms:listener destination="queue.orders" ref="myMessageListener"/>

</jms:jca-listener-container>

<jca-listener-container/>元素的属性

  • factory-id:
  • resource-adapter:
  • activation-spec-factory:
  • destination-resolver
  • message-converter
  • destination-type
  • response-destination-type
  • client-id
  • acknowledge
  • transaction-manager
  • concurrency
  • prefetch

 

posted @ 2020-07-14 22:24  codedot  阅读(1399)  评论(0编辑  收藏  举报