1 介绍
Java 消息服务(Java Message Service,JMS)应用程序接口是一个 Java 平台中关于面向消息中间件(MOM)的 API,用于在两个应用程序之间或分布式系统中发送消息,并进行异步通信。
Java 消息服务是一个与具体平台无关的 API,绝大多数 MOM 提供商都对 JMS 提供支持。Java 消息服务的规范包括两种消息模式,点对点和发布者/订阅者。
Java 消息服务支持同步和异步的消息处理,在某些场景下,异步消息是必须的,而且比同步消息操作更加便利。
Java 消息服务支持面向事件的方法接收消息。在应用系统开发时,Java 消息服务可以推迟选择面对消息中间件产品,也可以在不同的消息中间件之间进行切换。
以 ActiveMQ 为例进行 Spring 整合消息服务功能的实现。
2 JMS 的独立使用
在消息服务的使用前需要先开启消息服务器,如果是 window 系统,则可以直接启动 ActiveMQ 安装目录下 bin 目录中的 activemq.bat 启动消息服务器。除了要启动服务之外,还需要构建消息的发送端和接收端。发生端主要用来将包含业务逻辑的消息发送至服务器,消息接收端主要用于将服务器中的消息提取进行相应的处理。
1)发送端实现
public class Sender {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
// 创建连接
Connection connection = connectionFactory.createConnection();
// 创建一个会话
Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Destination destination = session.createQueue("my-queue");
// 创建生产者
MessageProducer producer = session.createProducer(destination);
for (int i = 0; i < 3; i++) {
TextMessage message = session.createTextMessage("大家好这是一个测试" + i);
Thread.sleep(1000);
// 提供消息生产者发送消息
producer.send(message);
}
session.commit();
session.close();
connection.close();
}
}
2)接收端实现
public class Receiver {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
// 创建连接
Connection connection = connectionFactory.createConnection();
connection.start();
// 创建一个会话
final Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
// 创建一个消息队列
Destination destination = session.createQueue("my-queue");
// 创建消费者
MessageConsumer consumer = session.createConsumer(destination);
int i = 0;
while(i < 3) {
i++;
// 消费消息
TextMessage textMessage = (TextMessage) consumer.receive();
session.commit();
System.out.println("收到消息:" + textMessage.getText());
}
session.close();
connection.close();
}
}
程序测试的顺序是,首先开启发送端,然后向服务器发送消息,接着开启接收端,就会受到发送端的消息。前提需要先开启 activeMQ 的服务器。
3 Spring 整合 ActiveMQ
整个消息的发送与接收过程非常简单,但是又许多冗余代码。Spring 进行了进一步整合,使用 ActiveMQ 如下。
1)Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 用于连接消息服务器,消息服务的基础-->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!-- Spring 将 ActiveMQ 中的操作封装到 JmsTemplate 中-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- 用于指定消息的目的地-->
<bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="HelloWorldQueue" />
</bean>
</beans>
2)发送端
有了以上的配置文件,Spring 就可以根据配置信息简化冗余代码。
public class HelloWorldSender {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("test/jms/spring/applicationContext.xml");
JmsTemplate template = context.getBean("jmsTemplate", JmsTemplate.class);
Destination destination = (Destination) context.getBean("destination");
template.send(destination, session -> session.createTextMessage("这是一个测试"));
}
}
3)接收端
public class HelloWorldReceiver {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("test/jms/spring/applicationContext.xml");
JmsTemplate template = context.getBean("jmsTemplate", JmsTemplate.class);
Destination destination = (Destination) context.getBean("destination");
TextMessage message = (TextMessage) template.receive(destination);
System.out.println(message.getText());
}
}
到这里就完成了 Spring 消息的发送接收操作。但是 template.receive(destination) 只能接收一次消息,如果为接收到消息,则会一直等待。但是一旦接收到消息,本次接收任务就会结束。虽然可以使用while(true) 循环监听消息服务器上的消息,但是更好的可以创建消息监听器。
消息监听器的使用方式如下:
1)创建消息监听器
用于监听消息,一旦又新消息,Spring 就会将消息引导至消息监听器,以方面用户进行相应的处理。
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage msg = (TextMessage) message;
try {
System.out.println(msg.getText());
} catch (Exception e) {
e.printStackTrace();
}
}
}
2)修改配置文件
为了使用消息监听器,需要在配置文件中注册消息容器,并将消息监听器注入监听器中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 用于连接消息服务器,消息服务的基础-->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!-- Spring 将 ActiveMQ 中的操作封装到 JmsTemplate 中-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!-- 用于指定消息的目的地-->
<bean id="destination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="HelloWorldQueue" />
</bean>
<!-- 监听器-->
<bean id="myMessageListener" class="org.springframework.test.jms.spring.MyMessageListener" />
<!-- 消费者配置-->
<bean id="javaConsumer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="destination" />
<property name="messageListener" ref="myMessageListener" />
</bean>
</beans>
通过以上的修改就可以进行消息监听的功能,一旦又消息传至消息服务器,就会被消息监听器监听到,并有 Spring 将消息内容引导至消息监听器的处理方法中,等待用户的进一步逻辑处理。
4 源码分析
虽然消息接收可是使用消息监听器的方式代替模板方法,但是在发送阶段模板,在 Spring 中必须要使用 JmsTemplate 提供的方法来进行发送操作,那么 Spring 整合消息服务分析得从 JmsTemplate 开始。
4.1 JmsTemplate
在与 Spring 整合的实例中,可以看到 Spring 采用了与 JDBC 一贯的套路,为我们提供了 JmsTemplate 来封装常用操作。查看类的结构图。
首先查看实现了 InitializingBean 接口的 JmsAccessor 类,实现方法是 afterPropertiesSet。
public void afterPropertiesSet() {
if (getConnectionFactory() == null) {
throw new IllegalArgumentException("Property 'connectionFactory' is required");
}
}
该方法只是验证了 connectionFactory 是否存在,没有其他的逻辑意义,所以我们转向示例代码进行分析。首先以发送为例,在 Spring 中发送消息可以通过 JmsTemplate 中提供的方法实现。
public void send(final Destination destination, final MessageCreator messageCreator);
// 使用方法
template.send(destination, session -> session.createTextMessage("这是一个测试"));
查看 send 的方法源码。
public void send(final Destination destination, final MessageCreator messageCreator) throws JmsException {
execute(session -> {
doSend(session, destination, messageCreator);
return null;
}, false);
}
这种方法和 JdbcTemplate 的实现风格类似,它们都是提取一个公共的方法作为最底层,最通用的功能实现,然后又通过回调函数的不同来区分个性化的功能。首先查看通用代码的抽取实现。
1)通用代码抽取
根据之前分析 JdbcTemplate 的经验,在 execute 中一定是封装了 Connection 和 Session 的创建操作。
public <T> T execute(SessionCallback<T> action, boolean startConnection) throws JmsException {
Assert.notNull(action, "Callback object must not be null");
Connection conToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
obtainConnectionFactory(), this.transactionalResourceFactory, startConnection);
if (sessionToUse == null) {
// 创建 Connection
conToClose = createConnection();
// 根据 Connection 创建 session
sessionToClose = createSession(conToClose);
// 是否开启向服务器推送连接信息,只有接收信息时需要,发送时不需要
if (startConnection) {
conToClose.start();
}
sessionToUse = sessionToClose;
}
if (logger.isDebugEnabled()) {
logger.debug("Executing callback on JMS Session: " + sessionToUse);
}
// 使用回调函数
return action.doInJms(sessionToUse);
}
catch (JMSException ex) {
throw convertJmsAccessException(ex);
}
finally {
// 关闭 session
JmsUtils.closeSession(sessionToClose);
// 释放连接
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);
}
}
在单独使用 activeMQ 时,我们知道发送一条消息,需要很多辅助代码,而这些代码都是一样的,所以 execute 方法的目的就是帮助来抽取这些代码,使我们更加专注于业务逻辑的实现。这些冗余代码包括创建 Connection,创建 Session,也包括关闭 Session,Connection。在准备工作结束后,将回调函数程序引入用户自定义实现的个性化处理。
2)发送消息的实现
有了基类的实现,使得 Spring 更加专注于个性的处理,即 Spring 使用 execute 方法封装了冗余代码,而将个性化的代码实现放在了回调函数 doInJms 函数中,在发送消息的功能中通过局部类实现回调函数。
new SessionCallback<Object>() {
@Override
public Object doInJms(Session session) throws JMSException {
doSend(session, destination, messageCreator);
return null;
}
};
这时的发送逻辑已经完全转向了 doSend 方法,这样使整个功能实现变得更加清晰。
protected void doSend(Session session, Destination destination, MessageCreator messageCreator)
throws JMSException {
Assert.notNull(messageCreator, "MessageCreator must not be null");
// 创建生产者
MessageProducer producer = createProducer(session, destination);
try {
// 创建消息
Message message = messageCreator.createMessage(session);
if (logger.isDebugEnabled()) {
logger.debug("Sending created message: " + message);
}
// 发送消息
doSend(producer, message);
// Check commit - avoid commit call within a JTA transaction.
if (session.getTransacted() && isSessionLocallyTransacted(session)) {
// Transacted session created by this template -> commit.
JmsUtils.commitIfNecessary(session);
}
}
finally {
JmsUtils.closeMessageProducer(producer);
}
}
在演示独立使用消息功能时,了解了发送消息的流程,在 Spring 整合中也是按照这个流程。比如根据 Destination 创建 MessageProducer,Message 并使用 MessageProducer 实例来发送消息。
3)接收消息
通常使用 jmsTemplate.receive(destination) 来接收简单的消息,那么 Spring 是如何实现这个功能的?
public Message receive(Destination destination) throws JmsException {
return receiveSelected(destination, null);
}
public Message receiveSelected(final Destination destination, @Nullable final String messageSelector) throws JmsException {
return execute(session -> doReceive(session, destination, messageSelector), true);
}
protected Message doReceive(Session session, Destination destination, @Nullable String messageSelector)
throws JMSException {
// 根据消费者来接收消息
return doReceive(session, createConsumer(session, destination, messageSelector));
}
protected Message doReceive(Session session, MessageConsumer consumer) throws JMSException {
try {
// Use transaction timeout (if available).
// 超时时间
long timeout = getReceiveTimeout();
// 工厂
ConnectionFactory connectionFactory = getConnectionFactory();
JmsResourceHolder resourceHolder = null;
if (connectionFactory != null) {
resourceHolder = (JmsResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory);
}
if (resourceHolder != null && resourceHolder.hasTimeout()) {
timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());
}
// 接收消息
Message message = receiveFromConsumer(consumer, timeout);
if (session.getTransacted()) {
// Commit necessary - but avoid commit call within a JTA transaction.
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this template -> commit.
JmsUtils.commitIfNecessary(session);
}
}
else if (isClientAcknowledge(session)) {
// Manually acknowledge message, if any.
if (message != null) {
message.acknowledge();
}
}
return message;
}
finally {
// 关闭消费者
JmsUtils.closeMessageConsumer(consumer);
}
}
rotected Message receiveFromConsumer(MessageConsumer consumer, long timeout) throws JMSException {
if (timeout > 0) {
// 接收消息,有超时时间
return consumer.receive(timeout);
}
else if (timeout < 0) {
return consumer.receiveNoWait();
}
else {
return consumer.receive();
}
}
实现的套路和发送差不多,通用还是使用 execute 函数来封装冗余的公共操作,而最终目标还是通过 consumer.receive 来接收消息,其中的过程就是对于 MessageConsumer 的创建以及一些辅助操作。
4.2 监听器容器
消息监听器是一个用于查看 JMS 目标等待消息到达的特殊 bean,一旦消息到达就可以获取消息,并通过调用 onMessage() 方法将消息传递给一个 MessageListener 实现。Spring 中消息监听器类型如下。
- SimpleMessageListenerContainer:最简单的消息监听器,只能处理固定数量的 JMS 会话,且不支持会话。
- DefaultMessageListenerContainer:建立在 SimpleMessageListenerContainer 之上,添加了对事务的支持。
- ServerSessionLinstenerContainer:与 DefaultMessageListenerContainer 相同,除了支持事务,还允许动态的管理 JMS 会话。
以 DefaultMessageListenerContainer 为例进行分析,了解消息监听器容器的实现。在使用消息监听器容器时,需要将自定义的消息监听器置入到容器中,这样容器才能在收到消息时,把消息转向监听器处理。查看 DefaultMessageListenerContainer 的类层次结构图。
此类实现了 InitializingBean 接口,所有先查看 afterPropertiesSet 方法中,其方法实现在父类 AbstractJmsListeningContainer 中。
// AbstractJmsListeningContainer.java
public void afterPropertiesSet() {
// 验证 connectFactory
super.afterPropertiesSet();
// 验证配置文件,子类实现
validateConfiguration();
// 初始化
initialize();
}
监听器容器的初始化包含3个方法,其中前两个用于属性的验证,比如 connectionFactory 或 destination 等属性是否为空。真正初始化逻辑委托给 initialize 方法执行。
// AbstractJmsListeningContainer.java
public void initialize() throws JmsException {
try {
// lifecycleMonitor 用于控制生命周期的同步处理
synchronized (this.lifecycleMonitor) {
this.active = true;
this.lifecycleMonitor.notifyAll();
}
// 初始化
doInitialize();
}
catch (JMSException ex) {
synchronized (this.sharedConnectionMonitor) {
ConnectionFactoryUtils.releaseConnection(this.sharedConnection, getConnectionFactory(), this.autoStartup);
this.sharedConnection = null;
}
throw convertJmsAccessException(ex);
}
}
// DefaultMessageListenerContainer.java
protected void doInitialize() throws JMSException {
synchronized (this.lifecycleMonitor) {
// 消费者数目
for (int i = 0; i < this.concurrentConsumers; i++) {
scheduleNewInvoker();
}
}
}
这里用到了 concurrentConsumers 属性。该属性说明如下。
消息监听器允许创建多个 Session 和 MessageConsumer 来接收消息。具体的个数由 concurrentConsumers 决定。需要注意,应该只是在 Destination 为 Queue 的时候才使用多个 MessageConsumer(Queue 中的一个消息只能被一个 Consumer 接收),虽然使用多个 MessageConsumer 时会提高消息处理的性能,但是消息处理的顺序得不到保证。由于并发的原因,消息处理的顺序可能和发送的顺序不一致。此外,不应该在 Destination 为 Topic 的使用使用多个 MessageConsumer,因为多个 MessageConsumer 会接收到同样的消息。
具体的逻辑实现委托给 scheduleNewInvoker 方法。
// DefaultMessageListenerContainer.java
private void scheduleNewInvoker() {
// 异步消息监听器调用器
AsyncMessageListenerInvoker invoker = new AsyncMessageListenerInvoker();
// 需要重新开启一个线程
if (rescheduleTaskIfNecessary(invoker)) {
// This should always be true, since we're only calling this when active.
this.scheduledInvokers.add(invoker);
}
}
// AbstractJmsListeningContainer.java
protected final boolean rescheduleTaskIfNecessary(Object task) {
if (this.running) {
try {
// 开启一个线程
doRescheduleTask(task);
}
catch (RuntimeException ex) {
logRejectedTask(task, ex);
this.pausedTasks.add(task);
}
return true;
}
else if (this.active) {
this.pausedTasks.add(task);
return true;
}
else {
return false;
}
}
// DefaultMessageListenerContainer.java
protected void doRescheduleTask(Object task) {
Assert.state(this.taskExecutor != null, "No TaskExecutor available");
this.taskExecutor.execute((Runnable) task);
}
分析源码可知,根据 concurrentConsumers 数量建立了对于数量的线程。doRescheduleTask 该方法中其实是在开启一个线程执行 Runnable。其中的任务 task 为一个 AsyncMessageListenerInvoker 实例。因此可以知道,Spring 根据 concurrentConsumers 数量建立了对于数量的线程,而每个线程多作为一个独立的接收者在循环接收消息。而 AsyncMessageListenerInvoker 是作为一个 Runnable 去执行的。
所以把目光放在 AsyncMessageListenerInvoker 类的 run 方法上。
// DefaultMessageListenerContainer.AsyncMessageListenerInvoker
public void run() {
// 并发控制
synchronized (lifecycleMonitor) {
activeInvokerCount++;
lifecycleMonitor.notifyAll();
}
boolean messageReceived = false;
try {
// 根据每个任务设置的最大消息处理数量而作不同的处理
// 小于 0 默认为无限制,一直接收消息
if (maxMessagesPerTask < 0) {
messageReceived = executeOngoingLoop();
}
else {
int messageCount = 0;
// 消息数量控制,一旦超出数量限制则停止循环
while (isRunning() && messageCount < maxMessagesPerTask) {
messageReceived = (invokeListener() || messageReceived);
messageCount++;
}
}
}
catch (Throwable ex) {
// 清理操作,包括关闭 session 等
clearResources();
if (!this.lastMessageSucceeded) {
// We failed more than once in a row or on startup -
// wait before first recovery attempt.
waitBeforeRecoveryAttempt();
}
this.lastMessageSucceeded = false;
boolean alreadyRecovered = false;
synchronized (recoveryMonitor) {
if (this.lastRecoveryMarker == currentRecoveryMarker) {
handleListenerSetupFailure(ex, false);
recoverAfterListenerSetupFailure();
currentRecoveryMarker = new Object();
}
else {
alreadyRecovered = true;
}
}
if (alreadyRecovered) {
handleListenerSetupFailure(ex, true);
}
}
finally {
synchronized (lifecycleMonitor) {
decreaseActiveInvokerCount();
lifecycleMonitor.notifyAll();
}
if (!messageReceived) {
this.idleTaskExecutionCount++;
}
else {
this.idleTaskExecutionCount = 0;
}
synchronized (lifecycleMonitor) {
if (!shouldRescheduleInvoker(this.idleTaskExecutionCount) || !rescheduleTaskIfNecessary(this)) {
// We're shutting down completely.
scheduledInvokers.remove(this);
if (logger.isDebugEnabled()) {
logger.debug("Lowered scheduled invoker count: " + scheduledInvokers.size());
}
lifecycleMonitor.notifyAll();
clearResources();
}
else if (isRunning()) {
int nonPausedConsumers = getScheduledConsumerCount() - getPausedTaskCount();
if (nonPausedConsumers < 1) {
logger.error("All scheduled consumers have been paused, probably due to tasks having been rejected. " +
"Check your thread pool configuration! Manual recovery necessary through a start() call.");
}
else if (nonPausedConsumers < getConcurrentConsumers()) {
logger.warn("Number of scheduled consumers has dropped below concurrentConsumers limit, probably " +
"due to tasks having been rejected. Check your thread pool configuration! Automatic recovery " +
"to be triggered by remaining consumers.");
}
}
}
}
}
以上方法主要根据变量 maxMessagePerTask 的值分情况处理。其中核心的处理就是调用 invokeListener 来接收消息并激活消息监听器,但是之所以分两种情况分开处理,正是考虑到在无限制循环接收消息的情况下,可以通过设置标志位 running 来控制消息接收的暂停和恢复,并维护当前消息监听器的数量。
// DefaultMessageListenerContainer.AsyncMessageListenerInvoker
private boolean executeOngoingLoop() throws JMSException {
boolean messageReceived = false;
boolean active = true;
while (active) {
synchronized (lifecycleMonitor) {
boolean interrupted = false;
boolean wasWaiting = false;
// 如果当前任务以及处于激活状态但是却给了暂时终止的命令
while ((active = isActive()) && !isRunning()) {
if (interrupted) {
throw new IllegalStateException("Thread was interrupted while waiting for " +
"a restart of the listener container, but container is still stopped");
}
if (!wasWaiting) {
// 如果并非处于等待状态则说明是第一次执行,需要将激活任务数量减少
decreaseActiveInvokerCount();
}
// 开始进入等待状态,等待任务恢复命令
wasWaiting = true;
try {
// 通过 wait 等待,也就是等待 notify 或者 notifyAll
lifecycleMonitor.wait();
}
catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
interrupted = true;
}
}
if (wasWaiting) {
activeInvokerCount++;
}
if (scheduledInvokers.size() > maxConcurrentConsumers) {
active = false;
}
}
if (active) {
// 核心是 invokeListener 方法
messageReceived = (invokeListener() || messageReceived);
}
}
return messageReceived;
}
如果按正常流程是不会进入 while 循环中的,而是直接进入函数 invokeListener 来接受消息并激活监听器,但是不可能让循环一直持续下去,需要考虑暂停线程或恢复线程的情况,isRunning 函数就派上用场了。
isRunning 方法用来检测标志位 this.running 状态而进行判断是否需要进入 while 循环。由于要维护当前线程激活数量,所以引入 wasWaiting 变量,用来判断线程是否处于等待状态。如果线程首次进入等待状态,则需要减少线程来激活数量计数器。
还有。线程等待不是一味的采用 while 循环来控制,这样会浪费 cpu 的时钟周期,给资源造成浪费。这里 Spring 采用全局控制变量 lifecycleMonitor 的 wait 方法来暂停线程,所有终止线程需要再次恢复的话,除了更改标志位 this.running 之外,还需要调用 lifecycleMonitor 的 notify 或 notifyAll 方法来使线程恢复。
接下来就是消息接收的处理。
// DefaultMessageListenerContainer.AsyncMessageListenerInvoker
private boolean invokeListener() throws JMSException {
// 当前接收消息的线程
this.currentReceiveThread = Thread.currentThread();
try {
// 初始化资源包括首次创建的时候创建 session 和 consumer
initResourcesIfNecessary();
// 接收消息并处理消息
boolean messageReceived = receiveAndExecute(this, this.session, this.consumer);
this.lastMessageSucceeded = true;
return messageReceived;
}
finally {
this.currentReceiveThread = null;
}
}
// AbstractPollingMessageListenerContainer.java
protected boolean receiveAndExecute(
Object invoker, @Nullable Session session, @Nullable MessageConsumer consumer)
throws JMSException {
// 事务管理器
if (this.transactionManager != null) {
// Execute receive within transaction.
TransactionStatus status = this.transactionManager.getTransaction(this.transactionDefinition);
boolean messageReceived;
try {
messageReceived = doReceiveAndExecute(invoker, session, consumer, status);
}
catch (JMSException | RuntimeException | Error ex) {
rollbackOnException(this.transactionManager, status, ex);
throw ex;
}
this.transactionManager.commit(status);
return messageReceived;
}
else {
// Execute receive outside of transaction.
return doReceiveAndExecute(invoker, session, consumer, null);
}
}
DefaultMessageListenerContainer 消息监听器容器在 SimpleMEssageListenerContainer 的基础上添加了事务的控制。此时,事务的特性就开始实现了,如果用户配置了 transactionManager,那么消息的接收就会被控制在事务之内,一旦出现任何异常情况都会被回滚,而回滚操作也会交由事务管理器统一处理。
// AbstractPollingMessageListenerContainer.java
private void rollbackOnException(PlatformTransactionManager manager, TransactionStatus status, Throwable ex) {
logger.debug("Initiating transaction rollback on listener exception", ex);
try {
manager.rollback(status);
}
catch (RuntimeException ex2) {
logger.error("Listener exception overridden by rollback exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Listener exception overridden by rollback error", ex);
throw err;
}
}
doReceiveAndExecute 方法包含了整个消息的接收处理过程,由于参杂事务,并没有复用模板中的方法。
// AbstractPollingMessageListenerContainer.java
protected boolean doReceiveAndExecute(Object invoker, @Nullable Session session,
@Nullable MessageConsumer consumer, @Nullable TransactionStatus status) throws JMSException {
Connection conToClose = null;
Session sessionToClose = null;
MessageConsumer consumerToClose = null;
try {
Session sessionToUse = session;
boolean transactional = false;
if (sessionToUse == null) {
sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
obtainConnectionFactory(), this.transactionalResourceFactory, true);
transactional = (sessionToUse != null);
}
if (sessionToUse == null) {
Connection conToUse;
if (sharedConnectionEnabled()) {
conToUse = getSharedConnection();
}
else {
conToUse = createConnection();
conToClose = conToUse;
conToUse.start();
}
sessionToUse = createSession(conToUse);
sessionToClose = sessionToUse;
}
MessageConsumer consumerToUse = consumer;
if (consumerToUse == null) {
consumerToUse = createListenerConsumer(sessionToUse);
consumerToClose = consumerToUse;
}
// 接收消息
Message message = receiveMessage(consumerToUse);
if (message != null) {
if (logger.isDebugEnabled()) {
logger.debug("Received message of type [" + message.getClass() + "] from consumer [" +
consumerToUse + "] of " + (transactional ? "transactional " : "") + "session [" +
sessionToUse + "]");
}
// 模板方法,当消息接收且在未处理前给与子类机会做处理
messageReceived(invoker, sessionToUse);
boolean exposeResource = (!transactional && isExposeListenerSession() &&
!TransactionSynchronizationManager.hasResource(obtainConnectionFactory()));
if (exposeResource) {
TransactionSynchronizationManager.bindResource(
obtainConnectionFactory(), new LocallyExposedJmsResourceHolder(sessionToUse));
}
try {
// 激活监听器处理消息
doExecuteListener(sessionToUse, message);
}
catch (Throwable ex) {
if (status != null) {
if (logger.isDebugEnabled()) {
logger.debug("Rolling back transaction because of listener exception thrown: " + ex);
}
status.setRollbackOnly();
}
handleListenerException(ex);
// Rethrow JMSException to indicate an infrastructure problem
// that may have to trigger recovery...
if (ex instanceof JMSException) {
throw (JMSException) ex;
}
}
finally {
if (exposeResource) {
TransactionSynchronizationManager.unbindResource(obtainConnectionFactory());
}
}
// Indicate that a message has been received.
return true;
}
// 接收到空消息
else {
if (logger.isTraceEnabled()) {
logger.trace("Consumer [" + consumerToUse + "] of " + (transactional ? "transactional " : "") +
"session [" + sessionToUse + "] did not receive a message");
}
// 空消息的处理
noMessageReceived(invoker, sessionToUse);
// Nevertheless call commit, in order to reset the transaction timeout (if any).
if (shouldCommitAfterNoMessageReceived(sessionToUse)) {
commitIfNecessary(sessionToUse, null);
}
// Indicate that no message has been received.
return false;
}
}
finally {
// 关闭资源
JmsUtils.closeMessageConsumer(consumerToClose);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), true);
}
}
上述代码看似复杂,但是辅助代码居多,其中最主要就是 doExecuteListener(sessionToUse, message) 方法,监听器的激活。
// AbstractMessageListenerContainer.java
protected void doExecuteListener(Session session, Message message) throws JMSException {
if (!isAcceptMessagesWhileStopping() && !isRunning()) {
if (logger.isWarnEnabled()) {
logger.warn("Rejecting received message because of the listener container " +
"having been stopped in the meantime: " + message);
}
rollbackIfNecessary(session);
throw new MessageRejectedWhileStoppingException();
}
try {
// 激活监听器
invokeListener(session, message);
}
catch (JMSException | RuntimeException | Error ex) {
rollbackOnExceptionIfNecessary(session, ex);
throw ex;
}
// 有事务的管理需要提交事务
commitIfNecessary(session, message);
}
protected void invokeListener(Session session, Message message) throws JMSException {
// 获取配置的监听器
Object listener = getMessageListener();
if (listener instanceof SessionAwareMessageListener) {
doInvokeListener((SessionAwareMessageListener) listener, session, message);
}
else if (listener instanceof MessageListener) {
doInvokeListener((MessageListener) listener, message);
}
else if (listener != null) {
throw new IllegalArgumentException(
"Only MessageListener and SessionAwareMessageListener supported: " + listener);
}
else {
throw new IllegalStateException("No message listener specified - see property 'messageListener'");
}
}
protected void doInvokeListener(SessionAwareMessageListener listener, Session session, Message message)
throws JMSException {
Connection conToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
if (!isExposeListenerSession()) {
// We need to expose a separate Session.
conToClose = createConnection();
sessionToClose = createSession(conToClose);
sessionToUse = sessionToClose;
}
// Actually invoke the message listener...
// 最终调用监听器的 onMessage 方法
listener.onMessage(message, sessionToUse);
// Clean up specially exposed Session, if any.
if (sessionToUse != session) {
if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) {
// Transacted session created by this container -> commit.
JmsUtils.commitIfNecessary(sessionToUse);
}
}
}
finally {
JmsUtils.closeSession(sessionToClose);
JmsUtils.closeConnection(conToClose);
}
}
// 如果配置的是 SessionAwareMessageListener 监听器,需要额外的处理
protected void doInvokeListener(SessionAwareMessageListener listener, Session session, Message message)
throws JMSException {
Connection conToClose = null;
Session sessionToClose = null;
try {
Session sessionToUse = session;
if (!isExposeListenerSession()) {
// We need to expose a separate Session.
conToClose = createConnection();
sessionToClose = createSession(conToClose);
sessionToUse = sessionToClose;
}
// Actually invoke the message listener...
// 最终调用监听器的 onMessage 方法
listener.onMessage(message, sessionToUse);
// Clean up specially exposed Session, if any.
if (sessionToUse != session) {
if (sessionToUse.getTransacted() && isSessionLocallyTransacted(sessionToUse)) {
// Transacted session created by this container -> commit.
JmsUtils.commitIfNecessary(sessionToUse);
}
}
}
finally {
JmsUtils.closeSession(sessionToClose);
JmsUtils.closeConnection(conToClose);
}
}
// 配置了事务管理器,需要提交事务
protected void commitIfNecessary(Session session, @Nullable Message message) throws JMSException {
// Commit session or acknowledge message.
if (session.getTransacted()) {
// Commit necessary - but avoid commit call within a JTA transaction.
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this container -> commit.
JmsUtils.commitIfNecessary(session);
}
}
else if (message != null && isClientAcknowledge(session)) {
message.acknowledge();
}
}
通过层层调用,最终提取监听器,并使用 listener.onMessage 对其进行激活,就是激活了用户自定义的监听处理逻辑。commitIfNecessary(session, message) 方法完成的功能是 session.commit()。完成消息服务的事务提交,涉及两个事务。在 DefaultMessageListenerContainer 增加的事务是通用的事务,也就是说如果我们在消息接收过程中产生了其它操作,一旦出现异常,包括插入数据库的数据都要进行回滚。此外,消息本身还有一个事务,当接受一个消息时,必须使用事务提交的方式,告诉消息服务器本地已经正常接收消息,消息服务器接收到本地的事务提交后,便可以将此消息删除,否则,当前消息会被其它接收者重新接收。