【Spring 源码深度解析】12 Spring 消息

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 增加的事务是通用的事务,也就是说如果我们在消息接收过程中产生了其它操作,一旦出现异常,包括插入数据库的数据都要进行回滚。此外,消息本身还有一个事务,当接受一个消息时,必须使用事务提交的方式,告诉消息服务器本地已经正常接收消息,消息服务器接收到本地的事务提交后,便可以将此消息删除,否则,当前消息会被其它接收者重新接收。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值