第29章 Spring框架对JMS的集成(二)

第29章 Spring框架对JMS的集成

29.3 Spring改进后的JMS实战格斗术

29.3.1 消息发送和同步接收

2. 同步消息处理场景浅析

外汇交易系统中大量使用JMS进行交互,比如系统与银行之间的交互,不过,这涉及银行内部的一些处理细节,通常我们是无法了解的,所以,我们改变以往的策略,转而构建一个简化版的JMS同步消息处理场景。在这个场景中,服务器端提供通用的用户验证服务,在客户端以JMS消息的形式发送验证请求之后,服务器端将接收这些验证请求并进行处理,一旦处理完毕,则将验证结果同样以JMS消息的形式发送给请求的客户端,客户端只有接收到通过验证后的反馈结果才能进行其他操作。整个情况如图29-1所示。

image-20220714171620947

虽然整个原型图(图29-1)看起来比较简单,但如果你有心的话,完全可以在这幅图的基础上扩展出更加实用的系统架构图。这丝亳没有夸张之意,对于我们所开发的Java EE应用程序来说如是,对于网络游戏服务器端的架构也同样如此。

从整个原型图(图29-1)上来看,客户端发送验证请求的JMS消息之后,需要同步等待服务端的验证结果,也就是说,客户端发送验证请求的消息之后,会直接同步接收验证的结果消息,只有接收到验证通过的消息之后,客户端才允许进行后继的处理工作。这其实就是我们将要一起来实现的那个同步消息处理场景,而至于服务端如何处理请求消息并返回验证结果,那是稍后在介绍Spring的异步消息支持的时候才需要操心的事情。现在,我们要考虑的是如何实现客户端的同步消息发送和接收的问题。

我们的初步设想是这样的,为验证请求信息预先配置一个专用消息队列,暂且称之为authRequestQueue,同时,为验证结果信息也同样配置一个专用消息队列,不妨称之为authResponseQueue。客户端将验证请求信息发送到authRequestQueue之后,就可以马上进入同步消息接收状态,从authResponseQueue中接收返回消息。将我们的原型图进一步细化之后,就可以得到如图29-2所示的示意图。

image-20220714171736947

应该说,我们的设想以及所看到的结果是很漂亮的。不过,它并不完美,如果说,这一场景中只有一个或者几个客户端需要验证服务,这样的设想实现是比较合适的,它在性能与队列管理复杂度之间取得了一个很好的平衡,但是,随着连接客户端的增加,我们是否要为每个客户端都维护一对儿authRequestQueue和authResponseQueue呢?虽然这么做可以保证性能不受损失,但消息队列的管理复杂度将直线上升,这是不可取的。

看来,我们得找其他方案。既然不想因消息队列的数量增加来引入过多的管理复杂度,我们就不引入多余的消息队列,可以使用单一的消息队列authRequestQueue来接收所有客户端发送的验证请求信息,并且使用同样是单一的一个消息队列authResponseQueue来返回验证后的结果信息。不过,为了保证客户端只接收到自己对应的验证信息,我们需要一个MessageSelector来帮助客户端过滤掉它不感兴趣的那部分。现在,我们有了另一副实现的设想图(见图29-3)。

image-20220715095706239

看起来,这比之前的设想要更加丰满和成熟一些,那么,它是否就完美了呢?事实上,也不尽然,使用MessageSelector会引入一定的性能损耗。不过,当前的方案还是可以使用的,姑且先按照这个方案实现客户端如下方代码清单所示。

final AuthRequest authReq = new AuthRequest();
authReq.setUserId("...");
authReq.setPassword("...");
...
    
String messageSelector = SystemMessageProperties.REQUEST_ID + "='" + requestId + "'";

getJmsTemplate().convertAndSend(getRequestDest(), authReq, new MessagePostProcessor() {
    
    
	public Message postProcessMessage(Message message) throws JMSException {
    
    
		message.setStringProperty(SystemMessageProperties.REQUEST_ID, requestId);
		return message;
    }
});

Message responseMessage = getJmsTemplate().receiveSelected(getResponseDest(), messageSelector);
processMessage(responseMessage);

SystemMessageProperties.REQUEST_ID只是一个常量,用于标志MessageSelector使用的JMS Message的Property。要以当前这个方案实现客户端的主要理由是,它基本上反映了JmsTemplate的绝大多数的技能,不是吗?

既然使用单一队列加MessageSelector的方案可能因为性能问题不能够在某些比较严格的场景下使用,我们现在给出最后一种方案,即使用Temporary Destination我们让每个客户端在发送验证请求消息之前,首先创建一个临时的消息队列(Temporary Queue),然后将这一创建好的临时消息队列设置为当前要发送消息的JMS ReplyTo。在服务端处理完请求消息之后,就可以根据当前消息的JMS ReplyTo,将验证信息返回到客户端指定的临时消息队列中,客户端直接从自己创建的临时消息队列中接收消息即可。现在,整个同步消息发送和接收的设想图就如图29-4所示。

image-20220715102154624

Temporary Destination的存活时间通常与创建它的Connection相挂钩。为了保证这一点儿,我们通常可以使用execute(SessionCallback)模板方法。下方代码清单是我们的实现代码(或者其他方式,只要保证消息的发送和接收与使用的Temporary Destination处于同一Connection下即可)。

final AuthRequest authReq = new AuthRequest();
authReq.setUserId("..");
authReq.setPassword("..");

getJmsTemplate().execute(new SessionCallback() {
    
    
	public Object doInJms(Session session) throws JMSException {
    
    
		Object Message message = session.createObjectMessage();
		message.setObject(authReq);
		TemporaryQueue responseDestination = session.ceateTemporaryQueue();
		message.setJMSReplyTo(responseDestination);
        
		MessageProducer producer = ssssion.createProducer(getRequestDest());
		try {
    
    
			producer.send(message);
        }
		finally {
    
    
            JmsUtils.closeMessageProducer(producer);
        }
        
		MessageConsumer consumer = session.createConsumer(responseDestination);
		try {
    
    
            Message receiveaMessage = consumer.receive(10000L);
			processMessage(receivedMessage);
        }
		finally {
    
    
			JmsUtils.closeMessageConsumer(consumer);
        }
		return null;
    }
}), true);

使用TemporaryQueue,可以避免事先创建大量的Destination,同时又不用因从单一消息队列接收消息而必须使用MessageSelector。可谓一举两得,何乐而不为呢?不过,世事无绝对,到底要采用哪种方式处理消息发送和同步的消息接收,需要你根据自己应用程序的当前场景来决定。

注意:以上所描述的每一种使用场景都不是普遍适用的,我们需要根据具体的应用场景来权衡利弊,并最终决定哪一种才是最适合当前应用场景的处理方式。

29.3.2 异步消息接收

在一个典型的J2EE场景中,处理异步消息的标准组件通常就是一个消息驱动Bean(Message-Driven Bean,简称MDB)。不过,要使用消息驱动Bean来处理异步消息通信的话,我们的应用就不得不依赖于EJB容器(EJB Container)。为了避免进行异步消息处理的应用程序对EJB容器的依赖,Spring提供了MessageListenerContainer。借助于MessageListenerContainer的支持,我们现在可以创建消息驱动POJO(Message-Driven POJO,简称MDP)来处理异步消息。不过,在开始创建真正可以运行的消息驱动POJO之前,我们有必要先了解一下MessageListenerContainer背后到底隐藏着什么样的奥秘。

小心:Spring提供的异步消息支持是在2.0之后引入的。如果你不得不使用Spring 1.x版本,那么只能获得JmsTemplate同步消息发送和接收的好处。

1. 了解MessageListenerContainer

MessageListenerContainer之所以叫做MessageListenerContainer,是因为用于处理消息的消息驱动POJO必须实现MessageListener接口,而MessageListenerContainer本身就负责管理这些实现了MessageListener的消息驱动POJO对象。这其实就跟消息驱动Bean的生存环境一样,所有的消息驱动Bean都需要实现MessageListener接口,而至于从哪里接收消息,接收到的消息最终如何分配给具体的消息驱动Bean来处理,那就完全是EJB容器的责任了。不负责任地讲,MessageListenerContainer实际上是接管了EJB容器在异步消息接收处理领域的部分职责。好在这种接管行为,确实为应用程序带来了更轻量级的异步消息处理方案,这也就是为什么消息驱动POJO现在是如此的受人欢迎。

往简单了讲,MessageListenerContainer就有两个职责:

  • 负责到指定的Destination接收符合处理条件的消息;

  • 将接收到的消息通过某种策略转发给指定类型的MessageListener实现类来处理。

不过,实际的MessageListenerContainer所要关注的不只是这两点。既然要将应用程序彻底地从JMS API的使用和管理中解放出来,真正的MessageListenerContainer实现类还需要关注Connection、Session等一系列对象的生命周期管理,异常的处理,甚至消息处理过程中涉及的本地事务与全局事务相关的种种关注点。说白了,MessageListenerContainer把“脏活儿累活儿”全部揽到自己的身上,我们只要在自己的MessageListener实现类中处理已经分给我们的Message就成了。

Spring框架默认提供了三种MessageListenerContainer实现类,如下所述。

(1)org.epringframework.jms.listener.SimpleMessageListenerContainer。

SimpleMessageListenerContainer是三种MessageListenerContainer实现类中最简单的,它采用JMS规范提供的标准异步消息接收方式来接收消息,即通过MessageConsumer.SetMessageListener(..)设定用于异步消息处理的MessageListener。为了能够扩展处理性能,SimpleMessageListenerContainer可以在初始化的时候同时指定创建多个Session用于消息处理。如果将SimpleMessageListenerContainer的处理逻辑简化一下,那么下方代码清单所示的原型代码可能会让你看起来更加亲切一些。

int coucurrentCount = 5;
Destination destination = // 接收消息使用到的JDestination
MessageListener messageListener = // 相应的MessageListener实现

// 最好是注入相应的ConnectionFactory实例
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
Connection connection = connectionFactory.createConnection();
Session[] concurrentSessions = new Session[coucurrentCount];
MessageConsumer[] concurrentConsumers = new MessageConsumer[coucurrentCount];

for(int i = 0; i < coucurrentCount; i++) {
    
    
	concurrentSessions[i] = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
	concurrentConsumers[i] = concurrentSessions[i].createConsumer(destination);
	concurrentConsumers[i].setMessageLiscener(messageListener);
}
connection.start();

我们的原型代码省略了异常的处理,同时许多可配置项也都写死到代码了。不过,使用SimpleMessageListenerContainer,这些都不再是问题。

SimpleMessageListenerContainer允许我们指定一个TaskExecutor用于消息处理的调度。如果没有明确指定任何TaskExecutor的话,为SimpleMessageListenerContainer指定的MessageListener将在JMSProvider的当前线程中处理消息,直到处理完成之前,将一直阻塞JMSProvider的当前线程。所以,SimpleMessageListenerContainer在性能扩展方面的发挥余地不是很大。

注意:有关TaskExecutor的详细情况和可用的实现类型,可以参考稍后的内容。

(2)org.springframework.jms.listener.DefaultMessageListenerContainer。

虽然DefaultMessageListenerContainer是用于异步消息处理的MessageListenerContainer实现类,但它本身的实现原理却有些“背道而驰”。实际上,DefaultMessageListenerContainer在接收指定Destination的消息的时候,不像SimpleMessageListenerContainer那样使用JMS规范的标准装备,而是反其道而行之,转而使用同步的接收方法。这当然有些“大逆不道”的味道,但这样的处理方式却可以给予DefaultMessageListenerContainer更大的发展空间。借助于相应的TaskExecuitor,DefaultMessageListenerContainer可以同时启动多个线程循环调用JMS的同步消息接收方法,而接收到的消息的处理,则完全是在自己的线程内进行,可以说互不干扰,这比依赖于JMSProvider的调度策略要灵活得多。

如果我们将DefaultMessageListenerContainer的实现原理进行简化,那么可以以下方代码清单中的原型代码为基础,做进一步的理解。

int coucurrentCount = 5;

ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
final JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);

Runnable processor = new Runnable() {
    
    
	public void run() {
    
    
		while(containerAlive) {
    
    
			Message message = jmsTemplate.receive(getDestination());
			getMessageListener().onMessage(message);
        }
    }
};

Thread[] threads = new Thread[coucurrentCount];
for(Thread thread : threads) {
    
    
	thread = new Thread(processor);
	thread.start();
}

当然,DefaultMessageListenerContainer不可能如此简单,不过,原理上是相似的,只不过比我们的原型做了更多抽象的工作而已。DefaultMessageListenerContainer内部的消息接收和分发不是像我们那样,每次都创建新的线程,而是将调度的工作转给了相应的TaskExeuctor。而且,DefaultMessageListenerContainer内部也不是真的使用了JmsTemplate,我们只是为了简化原型代码才引入JmsTemplate,DefaultMessageListenerContainer所做的工作要比原型所做的更多,更加严谨。

如果没有什么特殊需求的话,DefaultMessageListenerContainer将是我们使用最多的MessageListenerContainer实现。

(3)org.springframework.jms.listener.serversession.ServerSessionMessageListenerContainer。

JMS规范中规定的ServerSessionPool SPI并非强制所有的JMS Provider都提供支持,它是可选的。所以,使用ServerSessionMessageListenerContainer之前有一点需要确认,那就是你所使用的JMS Provider是否支持ServerSessionPool。如果答案是“否”的话,DefaultMessageListenerContainer才是合适的选择。

ServerSessionMessageListenerContainer是Spring使用JMS规范中“Chapter 8 JMS Application Server Facilities”一章中规定的标准API构建的MessageListenerContainer实现。如果我们想要使用JMS Provider构建于ServerSessionPool上的各种消息处理机制的话,那么可以直接使用ServerSessionMessageListenerContainer。

所有的MessageListenerContainer都实现了org.springframework.context.Lifecycle接口,所以,只要我们将具体的MessageListenerContainer实现类添加到Spring的IoC容器,IoC容器将保证相应的MessageListenerContainer得以运行,如下所示:

<bean id="messageListenerContainer" class=”org.springframework.jms.listener.DefaultMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>			
    <property name="destination" ref="authRequestQueue"/>		
	<property name="messageListener" ref="authReqListener"/>
</bean>

我们只为DefaultMessageListenerContainer指定了必要的配置项。如果需要做更多定制,比如替换默认的TaskExecutor等,可以参考MessageListenerContainer相应实现类的Javadoc文档。如果我们感觉以上的内容不够简洁,让人无法容忍,那好消息是,Spring 2.5之后,为基于XSD的配置引入了jms专有的命名空间,同样的配置内容,现在可以配置如下(使用之前不要忘了把jms的命名空间声明添加到配置文件中):

<jms:listener-container>
    <jms:listenerdestination="authRequestQueue" ref="authReqListener"/>
</jms:listener-container>

因为<jms:listener-container>有比较合理的默认值,所以,许多东西我们可以不做明确指定,比如connectionFactory。jms:listener-container>实际上定义有许多可以对其行为进行定制的属性,最明了的文档当然就是spring-jms.xsd。你可以在Spring 2.5发行包的dist\resources目录下找到它。不过通常不用这么做,如果有一个支持XSD的XMI编辑器,使用“Code Assist”功能就直接可以获得相应提示了。

有关MessageListenerContainer就先说到这里了。下面我们得来看一下MessageListenerContainer所服务的对象了,那就是MessageListener。哦,不对,更广义上讲,是消息驱动POJO(Message-Driven POJO)。

2. 消息驱动POJO

一个POJO能够转变为一个消息驱动POJO,最主要的差别就在于它头上的那顶帽子。当然,我是指MessageListener接口。MessageListener是消息驱动POJO身份的象征,所以,消息驱动POJO必须实现MessageListener接口。好在,这是一个消息驱动POJO所需要面对的唯一限制,除此之外,无论你继承什么父类,实现什么其他的接口,那都是你的自由了。一个普通的消息驱动POJO实现看起来就像如下这么简单:

public class YourMDPImpl implements MessageListener {
    
    
	public void onMessage(Message msg) {
    
    
		try {
    
    
			// 处理msg
        } catch (JMSException e) {
    
    
			throw JmsUtils.convertJmsAccessException(e);
        }
    }
}

这看起来跟普通的对象定义没有太大差别,不是吗?如果需要其他依赖的话,为其声明依赖,然后通过IoC容器注入即可。现在只要将这个YourMDPImpl添加到容器,并与相应的MessageListenerContainer相关联,它就可以借助于MessageListenerContainer的支持,进行异步的消息处理了,如下配置所示:

<jms:listener-container>
	<jms:listenerdestination=".." ref="yourMDP"/>
</jms:listener-container>

<bean id="yourMDP" class="">
	<!--可能的依赖注入对象-->
</bean>

整个基于MessageListenerContainer的消息驱动POJO开发,实际上就这么简单。在29.3.1节的第2小节中,为了演示消息的同步发送和接收,我们只实现了客户端部分的功能。现在,我们要给出服务端的实现了。在该场景中,服务端负责从authRequestQueue消息队列中异步接收客户端提交的验证请求消息,然后根据消息的内容调用服务端的相应服务对象进行验证工作。不管最终验证是否通过,服务端需要将验证结果以JMS消息的形式发送给请求的客户端。为了完成这样的功能,我们实现了如下方代码清单所示的消息驱动POJO。

public class AuthRequestListener implements MessageListener {
    
    
	private JmsTemplate jmsTemplate;
	private MessageConverter messageConverter = new SimpleMessageConverter();
	private IUserAuthService userAuthService;
    
	public void onMessage(Message msg) {
    
    
		try {
    
    
			AuthRequestauthRequest=(AuthRequest)getMessageConverter().fromMessage(msg);
			AuthResponseauthResponse*getUserAuthService()·processAuthRequest(authRequest);
			getJmsTemplate().convertAndSend(msg.getJMSReplyTo(),authResponse);
        } catch (JMSException e) {
    
    
			throw JmsUtils.convertJmsAccessException(e);
        }
    }
    
	// getter和setter方法定义...
}

AuthRequestListener将通过一个IUserAuthService对客户端通过JMS消息发送的验证请求进行处理,然后将验证结果再次打包成JMS消息,发送回请求的客户端。因为我们在客户端是通过创建TemporaryQueue来指定消息接收的Destination,所以,AuthRequestListener将取得的JMS ReplyTo对应的值作为最终消息发送的Destination,整个过程就像代码所展示的那么简单。最后,要让AuthRequestListener能够工作,需要将其添加到容器并与MessagelistenerContainer相关联,所以可以做下方代码清单所示的配置。

<jms:listener-container>
<jms:listener destination="authRequestQueue" ref="authReqListener" selector="JMSReplyTo is NOT NULL"/>
</jms:listener-container>

<bean id="authReqListener" class="..AuthRequestListener">
	<property name="userAuthService" ref="userAuthService"/>
	<property name="jmsTemplate" ref="producerTemplate"/>
</bean>

<bean id="userAuthService" class="..UserAuthServiceImpl">
</bean>

有时,只戴一顶帽子或许会感觉单调,或者说,这顶帽子提供的选择比较少,那么,可以尝试一下Spring为消息驱动POJO提供的另一顶帽子,即SessionAwareMessageListener

该接口定义如下:

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

SessionAwareMessageListener使得我们可以接触要处理的消息的同时,获得对应的Session的引用。如果我们要使用接收消息的Session做更多的工作,SessionAwareMessageListener可是不二之选哦!

Spring提供的所有MessageListenerContainer都支持MessageListener和SessionAwareMessageListener两种类型的消息驱动POJO。所以,使用SessionAwareMessageListener,除了会使我们绑定到Spring框架的API上之外,不会有任何其他的后顾之忧。不过,是否使用SessionAwareMessageListener,最终的决定权还是在你的手里。

好啦,或许你会说,这两顶帽子我看着都不爽,能不能不要?好的,没问题!如果我们不想自己的POJO实现MessageListener和SessionAwareMessageListener两者中任一接口,那么把要实现MessageListener或者SessionAwareMessageListener接口这样的强制贵任推给其他“人”就好了,而且Spring提供的MessageListenerAdapter实际上就是做这个工作的!

MessageListenerAdapter自身实现了MessageListener和SessionAwareMessageListener两个接口。所以,将它与某个MessageListenerContainer挂钩之后,MessageListenerAdapter就可以行使异步消息处理的职责。MessageListenerAdapter可以接受一个委派对象。当MessageListenerContainer调度MessageListenerAdapter进行消息处理的时候,MessageListenerAdapter实际上是把真正的处理委派给了它所持有的那个委派对象。要想我们的POJO不实现任何消息驱动POJO的相关接口,只要将它设置为MessageListenerAdapter所使用的委派对象即可。

实际上,我们完全可以将刚才的AuthRequestListener看作是原型版的MessageListenerAdapter,真正的MessageListenerAdapter所做的事情与其差不了多少。在AuthRequestListener中,IUserAuthService实际上就是我们不想实现任何消息驱动POJO的相关接口的POJO对象。为了在处理消息过程中使用到IUserAuthService的相关逻辑,我们通过MessageConverter来实现具体的Message与方法参数类型之间的转换。待IUserAuthService的处理方法完成后,再将处理结果打包成返回的消息并发送,而发送的Destination恰好是请求消息的JMSReplyTo所指定的。

MessageListenerAdapter的实现稍微复杂一些,但绝对跟AuthReguestListener十分接近,如下所述。

  • AuthRequestListener只能委派IUserAuthService类型的对象进行消息处理,而MessageListenerAdapter允许使用任意类型的委派对象,然后通过反射调用处理方法。

  • AuthRequestListener使用MessageConverter进行Message类型与具体对象类型之间的转换。MessageListenerAdapter也是如此。

  • AuthRequestListener使用JmsTemplate发送返回消息。MessageListenerAdapter因为有onMessage(message, session)方法公开的Session,所以,直接通过Session创建发送消息的相关对象。

  • AuthRequestListener将请求消息的JMSReplyTo作为返回消息的Destination。MessageListenerAdapter只有在委派对象处理方法有返回值的情况下才这么做,如果委派对象的处理方法不返回任何值(void),那么不会发送返回信息。

通过这样的对比,你是否已经可以实现一个自己的MessageListenerAdapter了呢?不过,要真的实现一个自己的MessageListenerAdapter还是免了吧!Spring提供的MessageListenerAdapter已经够用了。如果将我们的AuthRequestListener所实现的功能,转而使用MessageListenerAdapter来实现的话,最终的结果如下方代码清单所示。

<jms:listener-container>
	<jms:listener destination="authRequestQueue" ref="msgListener" selector="JMSRep1yTo is
NOT NULL"/>
</jms:listener-container>

<bean id="msgListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    下面这行是重点
	<property name="delegate" ref="userAuthService"></property>
	<property name="defaultListenerMethod" value="processAuthRequest"/>
</bean>
<bean id="userAuthService" class="..UserAuthServiceImpl">
</bean>

这里唯一需要注意的就是,我们为MessageListenerAdapter指定了委派对象为userAuthService的同时,还指定了其defaultListenerMethoa属性,该属性是告诉MessageListenerAdapter通过反射应该调用委派对象的哪个方法。如果不明确指定defaultListenerMethod值的话,MessageListenerAdapter将使用默认值“handleMessage”作为将被调用的委派对象的处理方法名称。

注意:有关MessageListenerAdapter的更多定制属性,请参考该类的Javadoc文档,比如MessageListenerAdapter所使用的MessageConverter和DestinationResolver等。

29.3.3 JMS相关异常处理

JMS规范(在7.x节)定义了一套完整的异常类层次体系,以javax.jms.JMSException为首,下设多种标准的异常类型,比如,IllegalstateException、InvalidDestinationException、Invalidselector Exception等。从异常层次体系的设定上来说,这没太多可挑剔的,但是,整个的JMSException异常层次体系是以checked exception为基准的。这就意味着,所有JMS相关的方法要么在方法内部捕获它们并处理,要么就在每个方法声明中声明这些可能抛出的异常类型。可是,对于应用程序来说,即使是捕获了这些异常,通常情况下也无法有效地处理。将JMSException的异常体系建立在unchecked exception类型基础上要更合理一些。

Spring在原有JMSException异常层次体系的基础上提出了一套使用unchecked exception声明的异常层次体系,以JmsException为代表(注意JmsException和JMSException的区别)。下面的其他多种异常类型则直接映射到JMSException的异常体系中同名的异常类,二者名称相同,只有unchecked exception和checked exception之分。表29-2给出了它们之间详细的对比关系。

image-20220719094807426

注意:要了解更多JmsException相关异常类型,请参考Spring的Javadoc文档,这些异常类型全部定义在org.springframework.jms包下。

org.springframework.jms.support.JmsUtils工具类的convertJImsAccessException(JMSException)方法,提供了从JMS的标准异常到Spring的JMS异常的转译(translate)支持。如果我们是使用JmsTemplate或者MessageListenerContainer,那么这样的异常转译已经由它们内部搞定了。只有在不使用它们的情况下,我们才会考虑JmsUtils的这项辅助能力。

29.3.4 框架内的事务管理支持

使用原始的JMS API进行消息的事务控制,是在创建Session的时候决定的,如下所示:

boolean sessionTransacted = true;
Connection connection = ...;
Session session = connection.createSession(sessionTransacted, Session.AUTO_ACKNOWLEDGE);
...

既然JmsTemplate已经对消息处理过程中涉及的API使用进行了封装,对于消息的事务控制自然也不能例外。实际上,Spring专门提供了针对JMS的PlatformTransactionManager实现,即JmsTransactionManager。这表明整个JMS相关的事务控制已经纳入了Spring的统一事务控制抽象层中,JmsTemplate和某些MessageListenerContainer的部分工作,就是使得自己能够加入到Spring统一事务抽象层这一大家族。

对于JMS相关的事务控制,我们可以有多种选择,如下所述。

  • 如果事务只需要局限于消息处理的话,我们可以直接指定JmsTemplate或者相应MessageListenerContainer的“sessionTransacted”属性为true,来使用JMS本地的事务控制,比如:

    ConnectionFactory connectionFactory = ...;
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    jmsTemp1ate.setSessionTransacted(true);
    ...
    
  • 在消息处理过程中,可能需要同时考虑消息以及数据访问相关的事务控制,这时,我们可以做进一步的划分,如下。

    • 如果消息的发送或者接收与数据访问之间不需要严格的事务界定,那么,可以让二者使用各自的事务管理器进行事务控制,即前者使用JmsTransactionManager。而后者使用DataSourceTranactionManager或者其他相关事务管理器。

    • 如果必须保证消息的发送或者接收与数据访问处于同一事务中,那么,我们就得使用JtaTransactionManager来管理全局性事务。不过,这需要我们使用的JMS Provider提供分布式的事务支持。

总地来说,在没有确实必要的情况下,应该尽量避免使用JtaTranactionManager开启全局事务控制。你需要根据具体场景来权衡各方案的复杂度与性能损耗之间的关系。

29.4 小结

消息机制在系统集成以及大型企业级应用中应用很广,可谓“不可或缺”之人才。JMS是Java平台为统一消息中间件产品的访问方式而提出的规范标准,但规范与应用之间毕竟存在差距。本章我们分析了JMS规范在应用中存在的一些问题或者说尴尬之处,然后引入Spring框架为简化基于JMS的应用开发所提供的各项“便利”,囊括了Spring框架为消息发送和同步消息接收、异步消息接收、JMS相关异常处理以及JMS相关场景下的事务处理等内容。希望本章的内容能够帮助你在以后的开发工作中更加高效、便捷地使用JMS!

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125947900