【译】Spring Framework Reference —— JMS部分


        本文是Spring官方文档JMS部分的翻译,可以直接查看原文

30.1 简介

        像集成JDBC一样,Spring也集成了JMS。

        JMS可以分为两块:消息生产者和消息消费者。Spring提供了JmsTemplate来做消息的生产和同步接收。对异步接收,Spring和Java EE的MDB风格类似,提供Message-Driven POJOs消息监听器。监听器可声明式的创建。

        org.springframework.jms.core 提供了JMS核心功能。包括JMS template(用来简化使用JMS创建和释放资源,类似JdbcTemplate之于JDBC)。Spring template可以为通用操作提供的现成的Helper方法;对某些更复杂的操作,可提供代理最终调用自定义的回调实现。此包中提供的方法包括:发送消息、同步接收消息、向用户暴露JMS session及消息生产者。
        org.springframework.jms.support 提供了对JMSException的转换。如将受检的JMSException系列异常转换为相匹配的非受检异常。如果自定义了JMSException的子类,这些异常将被封装为非受检的UncategorizedJmsException。
        org.springframework.jms.support.converter 提供了MessageConverter来做Java对象和JMS消息对象之间的转换。
        org.springframework.jms.support.destination提供了多种管理JMS destination的策略,比如提供了一个service locator用来获取JNDI中的destination。
        org.springframework.jms.annotation 提供注解驱动的能力(@JmsListener)
        org.springframework.jms.config 为jms监听及终端的配置提供了jms命名空间解析器
        org.springframework.jms.connection 提供了ConnectionFactory的独立实现。同时还包括一个PlatformTransactionManager的JMS实现(JmsTransactionManager),从而使得JMS事务可以无缝集成到Spring事务中

30.2 Using Spring JMS

30.2.1 JmsTemplate

        JmsTemplate是JMS核心包中最主要的类。它在内部处理了资源的创建及释放,简化了JMS的使用。在代码中使用JmsTemplate只需要实现一个回调接口。简单应用场景可以使用MessageCreator。MessageCreator回调接口使用JmsTemplate中提供的Session来创建消息。在更复杂的场景中应该使用SessionCallback回调。SessionCallback暴露了JMS session及MessageProducer。

        JMS API提供了两种发送消息的方法。一种可以自定义传送方式(delivery mode)、优先级、及生存时间等QOS参数;另一种不包含,使用默认值。JmsTemplate中有很多发送消息的方法,为了避免重复定义QOS参数,QOS的参数设定使用bean properties的形式。类似的,同步接收超时时长使用setReceiveTimeout来设置。

        一些JMS提供者允许在ConnectionFactory中设置QOS值。因此会导致MessageProducer的 send(Destination destination, Message message) 使用的QOS默认值与JMS标准中定义的有所不同。为了提供QOS值的一致管理,JmsTemplate必须开启自定义QOS值的设置,即将isExplicitQosEnabled设置为true。

        方便起见,JmsTemplate暴露了一些基本的请求、应答操作,允许发送消息并等待响应。

        笔记:JmsTemplate的实例在设置完成之后是线程安全的。因此你可以设置一个单例模式的JmsTemplate并将它注入到不同的装饰器中。重申一遍,JmsTemplate是有状态的,因为它包含一个ConnectionFactory的引用,但是此状态是不可改变的。

        在Spring Framework 4.1中,在JmsTemplate基础上封装了JmsMessagingTemplate,提供了对消息抽象的集成(如org.springframework.messaging.Message)。使得发送此类消息更加便利。

30.2.2 Connections

        JmsTemplate包含了对ConnectionFactory的引用。ConnectionFactory是JMS标准中规定的入口。可设置参数(包含供应商自定义参数及SSL设置)、创建到JMS的连接等。

        在EJB中使用JMS时,JMS供应商负责JMS接口的具体实现,因此他们可以直接参与声明式的事务管理及connection、session的共享。这时Java EE容器一般会要求在EJB部署描述中以resource-ref的形式声明JMS连接工厂,同时客户端app需要引用被管理的ConnectionFactory实现。

  • Caching Messaging Resources

        标准的API涉及到创建多个中间过程对象:
        ConnectionFactory -> Connection -> Session -> MessageProducer -> send
        可见在ConnectionFactory和最终的send操作之间需要创建及销毁三个过程对象。为了优化这些步骤,spring提供了两种ConnectionFactory。

  • SingleConnectionFactory

        SingleFactory在所有createConnection调用中始终返回同一个Connection,并忽略close()方法。在独立的环境中很方便,同一个连接可以用在多个JmsTemplate调用中,跨越多个事务。通常,SingleConnectionFactory从JNDI中获取一个标准ConnectionFactory引用。

  • CachingConnectionFactory

        CachingConnectionFactory扩展了SingleConnectionFactory的功能,并增加了Session、MessageProducer及MessageConsumers的缓存。初始缓存数量默认设置为1,可以通过sessionCacheSize来增加缓存数。注意实际缓存的session数量将会比基于acknowledgment模式的session缓存数量更多。所以当sessionCacheSize设置为1时,最多总共会缓存4个session实例,每种acknowledgment模式分别一个。
        MessageProducers和MessageConsumers分别缓存在各自独立的session中,在缓存时还考虑到生产者和消费者自有特有属性。MessageProducers基于destination来缓存,而MessageConsumers缓存是基于destination/selector/noLocal delivery flag及持久订阅者名称这些条件的组合。

30.2.3 Destination Management

        Destination和ConnectionFactory类似,都是可以在JNDI中存放、获取的JMS管理对象。在设置Spring application 上下文的时候可以使用JNDI工厂类 JndiObjectFactory 来向你的对象注入JMS destinations。但遇上destinations数量众多的场景、或者JMS提供者提供了高级的destination管理特性时,这种方式就显得比较笨拙。高级destination管理的例子如动态创建destination或者分层的destination命名空间。JmsTemplate将destination名称(代表着JMS destination对象)委托给DestinationResolver。DynamicDestinationResolver是JmsTemplate中默认的DestinationResolver实现,可处理动态destination。同时Spring提供了一个JndiDestinationResolver用来查找JNDI中的destination,如果查不到就回落到由DynamicDestinationResolver处理。

        通常情况下,JMS应用中的destination只能在运行时确定,而无法在项目部署时指定。比如事先约定命名、然后在运行时创建destination的系统组件之间会存在交互共享逻辑。虽然动态destination并不属于JMS规范,但是很多JMS供应商都实现了它。动态destination和临时destination有所不同,通常也不在JNDI中注册。动态destination的API因供应商而异。供应商可以简单直接丢弃JMS规范中的警告,直接根据默认的destination属性,调用TopicSession的createTopic(String topicName)或者QueueSession的createQueue(String queueName)方法。根据供应商的实现,DynamicDestinationResolver可以直接创建物理destination。

        JmsTemplate中的pubSubDomain域规定了JMS域(domain)如何被使用。此属性的默认值为false,表示点到点的队列,即决定了DestinationResolver接口的动态destination模式。

        可以使用JmsTemplate的defaultDestination属性来为destination设置默认值。此默认设置对发送和接收都有效。

30.2.4 Message Listener Containers

        JMS消息在EJB中最广泛的用途之一是消息驱动Bean。Spring侧相应的解决方案是创建消息驱动POJOs,来让用户从EJB容器解耦。在Spring Framework 4.1中,终端方法可以使用@JmsListener来开启监听。

        消息监听容器(message listener container)可以从JMS消息队列来接受消息,然后驱动已注入的MessageListener。listener container负责所有消息的接收、分发线程。消息监听容器沟通MDP和消息提供者,具体负责接收消息的登记、参与事务、获取和释放资源、异常转换等等。用户可以只专注于业务逻辑代码的编写,将JMS基础工作交给框架来实现。
        Spring中包含两种JMS消息监听容器,包含了不同的功能集。

  • SimpleMessageListenerContainer

        两种监听容器中较简单的一个。在启动时创建固定数量的JMS session和consumer,使用标准的JMS MessageConsumer.setMessageListener()方法来注册监听,然后交给JMS提供者自己实现的回调。此监听容器本身不允许运行时需求的动态变化,也无法参与外部管理的事务。此方式非常贴近独立的JMS标准——但并不兼容Java EE的JMS限制。

        笔记:虽然SimpleMessageListenerContainer不支持参与外部事务,但是支持JMS原生事务。简单的将sessionTransacted标识置为true或者在命名空间中将acknowledge属性设置为transacted,当异常发生时监听器将自动回滚。消息也将被重新发送一次。或者你可以使用'CLIENT_ACKNOWLEDGE'模式,此模式遇到任何异常将重新发送消息,但不会使用session事务,因此不会包含事务协议中的任何session操作(如发送响应消息)。

  • DefaultMessageListenerContainer

        最常用的一种监听容器。与SimpleMessageListenerContainer相反,它能适应运行时需求的动态改变,也可以参与外部事务。当配置了JtaTransactionManager时,接收到的每条消息都将和一个XA事务一起注册;因此可以利用到XA事务的语义。此监听容器在:对JMS提供方的低要求、外部事务管理的丰富功能和Java EE环境的兼容性这三者中达到了良好的平衡。

        你可以自定义容器的缓存级别。当禁用了任何缓存时,每次接受消息时都会重新创建全新的session。在高并发的非持久订阅场景可能会带来消息丢失。请针对场景选择合适的缓存级别。

        此容器能在消息队列(broker)宕掉之后自动恢复连接。默认情况下,简单的BackOff实现每5秒重试一次。可以参考ExponentialBackOff来实现自定义的BackOff,以便于更精细化控制重连策略。

        笔记:与SimpleMessageListenerContainer类似,此容器支持原生的JMS事务,并允许自定义acknowledgment模式。如果你能忍受JMS挂掉时偶尔的重复消息,那就强烈推荐使用外部事务管理。在业务逻辑中自定义重复消息检测步骤可能会覆盖这种情况,比如业务实体的存在性校验。或者协议表检测。比起将整个处理过程包含在一个XA事务中(给DefaultMessageListenerContainer配置JtaTransactionManager的方式)从而将消息接收包含在JMS消息接收中,这种方法会带来显著的效率提升。

30.2.5 Transaction management

        Spring为单个JMS ConnectionFactory的事务管理提供了JmsTractionManager类。这允许了JMS应用能够利用Spring自身提供的事务管理特性。JmsTransactionManager执行本地资源事务,将给定ConnectionFactory中的Connection/Session绑定到当前线程。JmsTemplate将自动检测此类事务并执行。
        在Java EE环境中,ConnectionFactory会管理Connection和Session池,以便资源可以在跨事务高效的重复利用。在独立的环境中,使用Spring的SingleConnectionFactory会共享JMS Connection,每个事务中会处理各自独立的Session。或者可以考虑使用JMS供应商提供的连接池适配器,如ActiveMQ的PooledConnectionFactory类。

        JmsTemplate也可以和JtaTransactionManager或具备XA能力的JMS ConnectionFactory一起使用,来执行分布式事务。请注意这需要用到JTA事务管理器和XA配置的ConnectionFactory(请查找你的Java EE服务器/JMS提供商文档)。

        在被管理和务管理事务的环境中重用代码可能会给使用JMS API从Connection创建Session时带来困扰。这是因为JMS API只有一个创建Session的工厂方法,需要的参数包括事务/acknowledgement模式。在被管理的事务环境中,这些值由环境事务基础架构来提供,因此提供商的封装会忽略JMS Connection中的设置。而在无管理的环境中,可以通过sessionTransacted和sessionAcknowledgeMode属性来设置。在使用PlatformTransactionManager时,template将始终收到一个事务化的JMS Session。

30.3 Sending a Message

        JmsTemplate包含很多有用的方法来发送消息。可以使用Destination对象来指定destination,也可以使用JNDI来根据名称查找destination。当没有指定destination时会使用默认值。

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");
            }
        });
    }
}


        上面例子中使用MessageCreator回调,从Session对象中创建一个文本消息。JmsTemplate构造函数中传递了一个ConnectionFactory引用。或者可以选择使用JavaBean风格来设置connectionFactory(使用Bean工厂或者普通Java代码)。
        另外,可以考虑继承Spring的JmsGatewaySupport辅助基类,它已经内置了很多JMS设置。

        send(String destinationName, MessageCreator creator)使用destination名称作为参数。如果这些名字是在JNDI中注册的,你应该讲template中的destinationResolver属性设置为一个JndiDestinationResolver实例。

        如果在创建JmsTemplate时指定了默认值,send(MessageCreator c) 将向默认destination发送消息。

30.3.1 Using Message Converters

        为了方便地直接发送领域对象模型,JmsTemplate有很多发送方法直接将Java对象作为参数。在JmsTemplate中包含convertAndSend()和receiveAndConvert()的重载方法来代理MessageConverter接口。MessageConverter接口简单约定了Java对象和JMS消息间的相互转换。默认的SimleMessageConverter提供了String/TextMessage,byte[]/ByteMessage,以及java.util.Map/MapMessage剪的转换方法。Converter可以让你专注于业务逻辑,从JMS消息的细节中解脱出来。

        目前沙箱中已经包含了MapMessageConverter,它使用反射来转换JavaBean和MapMessage。其他流行的实现选择比如使用XML处理工具(如JAXB、Castor、XMLBeans或者XStream)来创建TextMessage来表示一个对象。

        在converter类中一般无法封装一些message头或消息体之类的属性,此时你需要用到MessagePostProcessor接口。它能让你在消息转换之后、发送之前直接操作消息。下面的例子展示了在一个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}
    }
}

30.3.2 SessionCallback and ProducerCallback

        发送消息的需求如此普遍,你可能会在JMS Session或者MessageProducer上进行各种操作。SessionCallback和ProducerCallback能够直接暴露JMS Session和Session/MessageProducer对。请尝试一下JmsTemplate的execute()方法。

30.4 Receiving a Message

30.4.1 Synchronous Reception

        虽然JMS通常是异步接收的,但是它也具备同步消费消息的能力。请见重载的receive(...)方法。同步接收时,调用线程将一直阻塞直到收到一条消息。显然这里有永远阻塞的风险。receiveTimeout属性可以定义等待时间,超时后会放弃。

30.4.2 Asynchronous Reception - Message-Driven POJOs

        笔记:Spring支持终端侧的注解监听。只需要使用@JmsListener注解,Spring会帮你自动注册监听器。这也是目前设置异步监听最简单的方式。

        和EJB中的MDB类似,Message-Driven 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自带的监听容器的例子:

<!-- 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>

        请参考Spring文档来详细了解各消息监听容器的细节。

30.4.3 SessionAwareMessageListener interface

        Spring定义了SessionAwareMessageListener接口来提供与JMS的MessageListener接口类似约定,但是加入了操作JMS Session的能力。

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {
    void onMessage(Message message, Session session) throws JMSException;
}

        如果开发者希望MDPs能够响应任何接收到任何类型的消息,可以选择让自己的MDPs实现此接口(来替代标准的JMS MessageListener),从而直接操作Session。Spring提供的所有的消息监听容器实现类都支持实现MessageListener和SessionAwareMessageListener这任意一个接口的MDPs。
        使用SessionAwareMessageListener的坏处是你将和Spring绑定在一起。开发者和架构师们请自行评估,权衡利弊。

        请注意和标准MessageListener不同的是,SessionAwareMessageListener接口的onMessage(..)方法会抛出JMSException异常。客户端需要自行处理此异常。

30.4.4 the MessageListenerAdapter

        MessageListenerAdapter类是spring异步消息接口的最后一个组件:简单来说,它允许你暴露任何类来当做一个MDP(当然有限制条件)。        

        考虑下面的接口。注意无论它扩展的是MessageListener还是SessionAwareMessageListener,都能够被MessageListenerAdapter当做一个MDP来使用。不同的消息处理方法由Message类型来区分。

public interface MessageDelegate {
    void handleMessage(String message);
    void handleMessage(Map message);
    void handleMessage(byte[] message);
    void handleMessage(Serializable message);
}

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

        需要特别注意的是上面MessageDelegate的实现没有任何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>

        如下是一个另一个只能处理TextMessage消息的MDP。注意最终处理消息的方法是receive方法(MessageListenerAdapter默认的消息处理方法是handleMessage),这同样是可以配置的。注意此receive方法只能接受TextMessage类型的消息。

public interface TextMessageDelegate {
    void receive(TextMessage message);
}

public class DefaultTextMessageDelegate implement 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接收到JMS TextMessage类型之外的消息时,将抛出一个IllegaStateException异常(随后此异常会被吞掉)。MessageListenerAdapter类的另一个能力是当处理方法返回值不为空时,可以自动发送响应消息:

public interface ResponsiveTextMessageDelegate {
    String receive (TextMessage message);
}

public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

        如果上面的DefaultResponsiveTextMessageDelegate和MessageListenerAdapter一起使用,那么默认配置下任何receive方法的非空返回值将被自动转为TextMessage。然后发送给原始消息的JMS Reply-To中定义的destination(如果存在的话);如果MessageListenerAdapter配置了默认Destination,它将被发给默认Destination。如果没有任何可用的Destination,将抛出一个InvalidDestinationException异常(此异常将不会被吞掉,并将在调用栈中传播)。

30.4.5 Processing messages within transactions

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

        可以简单的设置监听容器的sessionTransacted标识来激活本地资源事务。激活之后,所有的消息监听将在JMS事务中调用,监听操作失败会回滚消息接收。SessionAwareMessageListener的发送响应消息也属于同一个本地事务。但是其他资源操作(如数据库连接等)是独立的。通常来说,应该在监听器实现中检测消息是否重复,来防止数据库提交成功但是消息处理失败的情况。

<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(默认情况下由Java EE容器的事务管理子系统来处理)。注意底层的JMS ConnectionFactory需要有XA能力,并在注册在JTA事务协调器中(请检查你的Java EE服务器的JNDI资源配置)。这允许database和接收消息处在同一个事务中(使用同样的commit语义,花费XA事务日志开销)。

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

30.5 Support for JCA message Endpoints

        从2.5版本开始,Spring就支持基于JCA的消息监听容器。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查找:

<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可以使用一个特定环境下的线程池——通常通过SimpleTaskWorkManager的asyncTaskExecutor属性来配置。如果你碰上需要使用多适配器的场景,可以考虑为所有ResourceAdapter配置一个共享线程池。
        在某些使用环境中(如WebLogic 9及以上版本),整个ResourceAdapter对象可以从JNDI获取。基于Spring的消息监听器直接与基于服务器的ResourceAdapter交互,使用的是服务器内置的WorkManager。

        更详细的资料请查阅JmsMessageEndpointManager,JmsActivationSpecConfig及ResourceAdapterFactoryBean文档。

        Spring也提供了不与JMS绑定的JCA消息终端管理:org.springframework.jca.endpoint.GenericMessageEndpointManager。此组件允许使用任意的消息监听类别(如CCI MessageListener)和任意特定提供商的ActivationSpec对象。请在你的JCA提供商文档中查阅连接器的实际能力。关于Spring配置细节,请查阅GenericMessageEndpointManager文档。

        笔记:基于JCA的消息终端管理类似于EJB 2.1中的消息驱动Bean;它们使用相同的底层资源约定。和EJB 2.1的MDB一样,你的JCA提供商支持的任意消息监听接口都可以在Spring上下文中使用。然而Spring对JMS提供更便利的支持,因为JMS使用的更加普遍。

30.6 Annotation-driven listener endpoints

        使用注解监听是接受异步消息的最简单方法。简单来说,它允许你将一个spring bean的方法暴露为JMS监听终端。

@Component
public class MyService {

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

        在上面这段代码中,每当myDestination中有可用的消息,processOrder就会被调用(在这个例子中,JMS消息的内容类似由MessageListenerAdapter提供的)。
        注解终端的基础架构会使用JmsListenerContainerFactory为每一个注解方法创建一个消息监听容器。此容器并没有在application上下文中注册,但是能够容易的被JmsListenerEndpointRegistry bean找到并管理。

        笔记:@JmsListener在Java 8中是一个可重复的注解,你可以为同一个方法增加多个JmsListener来指定多个JMS destination。在Java 6 或者7中,可以使用@JmsListeners注解。

30.6.1 Enable listener endpoint annotations

        开启@JmsListener注解只需要在一个@Configuration类上增加@EnableJms注解。

@Configuration
@EnableJms
public class AppConfig {

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

        默认情况下,基础架构会查询名为jmsListenerContainerFactory的bean,并使用它来创建消息监听容器。这里我们忽略了JMS基础架构的设置,processOrder方法可以在一个线程池中被调用,线程池核心数为3,最大线程数为10。
        你可以通过实现JmsListenerConfigurer接口来自定义监听容器工厂。仅在存在没有指定容器工厂的终端时时,默认值才是必须的。

        习惯使用XML配置的开发者可以这样配置:

<jms:annotation-driven/>

<bean id="jmsListenerContainerFactory"
        class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destinationResolver" ref="destinationResolver"/>
    <property name="concurrency" value="3-10"/>
</bean>

30.6.2 Programmatic endpoint registration

        JmsListenerEndpoint提供了JMS终端模型,并负责该模型的容器配置。除了使用注解之外,基础架构也允许你用编程的方式来配置终端。

@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来注册所有终端。

30.6.3 Annotated endpoint method signature

        目前为止我们都是向终端注入一个简单的String,实际上它有着非常灵活的方法签名。现在让我们重写它并注入一个带有自定义头部的Order:

@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。注意此消息同时包含自定义和JmsHeaders定义的标准头
  • @Header-注解的方法参数被用来提取一个特定的头部值,包括标准JMS头
  • @Headers注解参数必须指定给一个java.util.Map,用来获取所有头
  • 不被支持的(如Message、Session等)、且无注解的元素被视为有效载荷。可以明确的给它们添加@Payload注解。也可以通过添加@Valid注解来开启校验。

        注入Spring的Message抽象可以获取特定消息的所有信息,而无需依赖特定传输API。

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

        可以自己扩展DefaultMessageHandlerMethodFactory来处理额外的方法参数。同时你也可以自定义转换和校验规则。
        比如我们需要在处理Order之前检查它是否有效:

@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;
    }
}

30.6.4 Response management

        MessageListenerAdapter允许你的方法有非空返回值。此时返回值将被封装在一个 javax.jms.Message中,发往原始消息的JMSReplyTo头中定义的destination或者监听自己默认的destination。监听默认的destination可以通过@SendTo注解来设置。

        考虑processOrder方法将返回一个OrderStatus,下面将展示如何发送响应:

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

        笔记:如果你有多个@JmsListener注解的方法,可以将@SendTo注解用于类上来共享默认响应destination。
        如果需要自定义消息头,可以返回一个Message:

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

        如果响应destination是运行时实时计算的,可以将response封装在一个JmsResponse中,直接指定一个destination。例如:

@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");
}

30.7 JMS namespace support

        Spring提供了XML命名空间来简化JMS配置。详见这里,后略。

转载于:https://my.oschina.net/landas/blog/858784

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值