当前位置: 首页 > 工具软件 > Fly Template > 使用案例 >

Spring源码设计模式:模板方法(Method Template)之下篇

车诚
2023-12-01

目录

模板模式

JmsTemplate

TransactionTemplate

JndiTemplate

参考文章


上篇:Spring源码设计模式:模板方法(Method Template)之上篇

模板模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

在spring源码中使用到模板模式的有以下几类(在Spring中大多数模板方式都是行为接口的定义:*Callback):

  • RestTemplate  Http Restful接口请求模板
  • AsyncRestTemplate 异步Http Restful接口请求模板
  • JdbcTemplate JDBC关系型数据库操作模板
  • HibernateTemplate Hibernate关系型数据库操作模板
  • JmsTemplate 消息队列模板
  • TransactionTemplate 编程式事务模板

特别说明:RedisTemplate Redis缓存数据操作模板(ReidsTemplate属于 spring-boot-starter-data-redis)

<!--Redis RedisTemplate-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

JmsTemplate

JmsTemplate简化同步JMS访问代码的Helper类。如果要使用动态目标创建,则必须使用“ pubSubDomain”属性指定要创建的JMS目标的类型。对于其他操作,这不是必需的。点对点(队列)是默认域。

JMS会话的默认设置为“未交易”和“自动确认”。根据Java EE规范的定义,当在活动事务中创建JMS会话时,无论JTA事务还是Spring管理的事务,都将忽略事务和确认参数。要为本机JMS使用配置它们,请为bean属性“ sessionTransacted”和“ sessionAcknowledgeMode”指定适当的值。

此模板分别使用DynamicDestinationResolver和SimpleMessageConverter作为解析目标名称或转换消息的默认策略。这些默认值可以通过“ destinationResolver”和“ messageConverter” bean属性来覆盖。

注意:与该模板一起使用的ConnectionFactory应该返回池化的Connections(或单个共享Connection)以及池化的Sessions和MessageProducers。否则,临时JMS操作的性能将受到影响。最简单的选择是将Spring提供的SingleConnectionFactory用作目标ConnectionFactory的装饰器,以线程安全的方式重用单个JMS Connection。对于通过此模板发送消息的目的而言,这通常通常已经足够了。在Java EE环境中,请确保通过JNDI从应用程序的环境命名上下文中获取ConnectionFactory。应用程序服务器通常在此处公开池化的,具有事务意识的工厂。

类所在位置:

package org.springframework.jms.core;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.TemporaryQueue;

import org.springframework.jms.JmsException;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.connection.JmsResourceHolder;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.QosSettings;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.SimpleMessageConverter;
import org.springframework.jms.support.destination.JmsDestinationAccessor;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

/**
 * Helper class that simplifies synchronous JMS access code.
 *
 * <p>If you want to use dynamic destination creation, you must specify
 * the type of JMS destination to create, using the "pubSubDomain" property.
 * For other operations, this is not necessary. Point-to-Point (Queues) is the default
 * domain.
 *
 * <p>Default settings for JMS Sessions are "not transacted" and "auto-acknowledge".
 * As defined by the Java EE specification, the transaction and acknowledgement
 * parameters are ignored when a JMS Session is created inside an active
 * transaction, no matter if a JTA transaction or a Spring-managed transaction.
 * To configure them for native JMS usage, specify appropriate values for
 * the "sessionTransacted" and "sessionAcknowledgeMode" bean properties.
 *
 * <p>This template uses a
 * {@link org.springframework.jms.support.destination.DynamicDestinationResolver}
 * and a {@link org.springframework.jms.support.converter.SimpleMessageConverter}
 * as default strategies for resolving a destination name or converting a message,
 * respectively. These defaults can be overridden through the "destinationResolver"
 * and "messageConverter" bean properties.
 *
 * <p><b>NOTE: The {@code ConnectionFactory} used with this template should
 * return pooled Connections (or a single shared Connection) as well as pooled
 * Sessions and MessageProducers. Otherwise, performance of ad-hoc JMS operations
 * is going to suffer.</b> The simplest option is to use the Spring-provided
 * {@link org.springframework.jms.connection.SingleConnectionFactory} as a
 * decorator for your target {@code ConnectionFactory}, reusing a single
 * JMS Connection in a thread-safe fashion; this is often good enough for the
 * purpose of sending messages via this template. In a Java EE environment,
 * make sure that the {@code ConnectionFactory} is obtained from the
 * application's environment naming context via JNDI; application servers
 * typically expose pooled, transaction-aware factories there.
 *
 * @author Mark Pollack
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 1.1
 * @see #setConnectionFactory
 * @see #setPubSubDomain
 * @see #setDestinationResolver
 * @see #setMessageConverter
 * @see javax.jms.MessageProducer
 * @see javax.jms.MessageConsumer
 */
public class JmsTemplate extends JmsDestinationAccessor implements JmsOperations {

核心方法:

 

//---------------------------------------------------------------------------------------
	// JmsOperations execute methods
	//---------------------------------------------------------------------------------------

	@Override
	@Nullable
	public <T> T execute(SessionCallback<T> action) throws JmsException {
		return execute(action, false);
	}

	/**
	 * Execute the action specified by the given action object within a
	 * JMS Session. Generalized version of {@code execute(SessionCallback)},
	 * allowing the JMS Connection to be started on the fly.
	 * <p>Use {@code execute(SessionCallback)} for the general case.
	 * Starting the JMS Connection is just necessary for receiving messages,
	 * which is preferably achieved through the {@code receive} methods.
	 * @param action callback object that exposes the Session
	 * @param startConnection whether to start the Connection
	 * @return the result object from working with the Session
	 * @throws JmsException if there is any problem
	 * @see #execute(SessionCallback)
	 * @see #receive
	 */
	@Nullable
	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) {
				conToClose = createConnection();
				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 {
			JmsUtils.closeSession(sessionToClose);
			ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);
		}
	}

	@Override
	@Nullable
	public <T> T execute(ProducerCallback<T> action) throws JmsException {
		String defaultDestinationName = getDefaultDestinationName();
		if (defaultDestinationName != null) {
			return execute(defaultDestinationName, action);
		}
		else {
			return execute(getDefaultDestination(), action);
		}
	}

	@Override
	@Nullable
	public <T> T execute(final @Nullable Destination destination, final ProducerCallback<T> action) throws JmsException {
		Assert.notNull(action, "Callback object must not be null");
		return execute(session -> {
			MessageProducer producer = createProducer(session, destination);
			try {
				return action.doInJms(session, producer);
			}
			finally {
				JmsUtils.closeMessageProducer(producer);
			}
		}, false);
	}

	@Override
	@Nullable
	public <T> T execute(final String destinationName, final ProducerCallback<T> action) throws JmsException {
		Assert.notNull(action, "Callback object must not be null");
		return execute(session -> {
			Destination destination = resolveDestinationName(session, destinationName);
			MessageProducer producer = createProducer(session, destination);
			try {
				return action.doInJms(session, producer);
			}
			finally {
				JmsUtils.closeMessageProducer(producer);
			}
		}, false);
	}

SessionCallback,BrowserCallback和ProducerCallback :

package org.springframework.jms.core;

import javax.jms.JMSException;
import javax.jms.Session;

import org.springframework.lang.Nullable;

/**
 * Callback for executing any number of operations on a provided {@link Session}.
 *
 * <p>To be used with the {@link JmsTemplate#execute(SessionCallback)} method,
 * often implemented as an anonymous inner class or as a lambda expression.
 *
 * @author Mark Pollack
 * @since 1.1
 * @param <T> the result type
 * @see JmsTemplate#execute(SessionCallback)
 */
@FunctionalInterface
public interface SessionCallback<T> {

	/**
	 * Execute any number of operations against the supplied JMS {@link Session},
	 * possibly returning a result.
	 * @param session the JMS {@code Session}
	 * @return a result object from working with the {@code Session}, if any
	 * (or {@code null} if none)
	 * @throws javax.jms.JMSException if thrown by JMS API methods
	 */
	@Nullable
	T doInJms(Session session) throws JMSException;

}

package org.springframework.jms.core;

import javax.jms.JMSException;
import javax.jms.QueueBrowser;
import javax.jms.Session;

import org.springframework.lang.Nullable;

/**
 * Callback for browsing the messages in a JMS queue.
 *
 * <p>To be used with {@link JmsTemplate}'s callback methods that take a
 * {@link BrowserCallback} argument, often implemented as an anonymous
 * inner class or as a lambda expression.
 *
 * @author Juergen Hoeller
 * @since 2.5.1
 * @param <T> the result type
 * @see JmsTemplate#browse(BrowserCallback)
 * @see JmsTemplate#browseSelected(String, BrowserCallback)
 */
@FunctionalInterface
public interface BrowserCallback<T> {

	/**
	 * Perform operations on the given {@link javax.jms.Session} and
	 * {@link javax.jms.QueueBrowser}.
	 * @param session the JMS {@code Session} object to use
	 * @param browser the JMS {@code QueueBrowser} object to use
	 * @return a result object from working with the {@code Session}, if any
	 * (or {@code null} if none)
	 * @throws javax.jms.JMSException if thrown by JMS API methods
	 */
	@Nullable
	T doInJms(Session session, QueueBrowser browser) throws JMSException;

}

package org.springframework.jms.core;

import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.springframework.lang.Nullable;

/**
 * Callback for sending a message to a JMS destination.
 *
 * <p>To be used with {@link JmsTemplate}'s callback methods that take a
 * {@link ProducerCallback} argument, often implemented as an anonymous
 * inner class or as a lambda expression.
 *
 * <p>The typical implementation will perform multiple operations on the
 * supplied JMS {@link Session} and {@link MessageProducer}.
 *
 * @author Mark Pollack
 * @since 1.1
 * @param <T> the result type
 * @see JmsTemplate#execute(ProducerCallback)
 * @see JmsTemplate#execute(javax.jms.Destination, ProducerCallback)
 * @see JmsTemplate#execute(String, ProducerCallback)
 */
@FunctionalInterface
public interface ProducerCallback<T> {

	/**
	 * Perform operations on the given {@link Session} and {@link MessageProducer}.
	 * <p>The message producer is not associated with any destination unless
	 * when specified in the JmsTemplate call.
	 * @param session the JMS {@code Session} object to use
	 * @param producer the JMS {@code MessageProducer} object to use
	 * @return a result object from working with the {@code Session}, if any
	 * (or {@code null} if none)
	 * @throws javax.jms.JMSException if thrown by JMS API methods
	 */
	@Nullable
	T doInJms(Session session, MessageProducer producer) throws JMSException;

}

JMS测试示例:

package org.springframework.jms.core;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import javax.jms.TextMessage;
import javax.naming.Context;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.jms.InvalidClientIDException;
import org.springframework.jms.InvalidDestinationException;
import org.springframework.jms.InvalidSelectorException;
import org.springframework.jms.JmsException;
import org.springframework.jms.JmsSecurityException;
import org.springframework.jms.MessageEOFException;
import org.springframework.jms.MessageFormatException;
import org.springframework.jms.MessageNotReadableException;
import org.springframework.jms.MessageNotWriteableException;
import org.springframework.jms.ResourceAllocationException;
import org.springframework.jms.TransactionInProgressException;
import org.springframework.jms.TransactionRolledBackException;
import org.springframework.jms.UncategorizedJmsException;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.connection.TransactionAwareConnectionFactoryProxy;
import org.springframework.jms.support.JmsUtils;
import org.springframework.jms.support.QosSettings;
import org.springframework.jms.support.converter.SimpleMessageConverter;
import org.springframework.jms.support.destination.JndiDestinationResolver;
import org.springframework.jndi.JndiTemplate;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;

/**
 * Unit tests for the JmsTemplate implemented using JMS 1.1.
 *
 * @author Andre Biryukov
 * @author Mark Pollack
 * @author Stephane Nicoll
 */
class JmsTemplateTests {

	private Context jndiContext;

	private ConnectionFactory connectionFactory;

	protected Connection connection;

	private Session session;

	private Destination queue;

	private QosSettings qosSettings = new QosSettings(DeliveryMode.PERSISTENT, 9, 10000);


	/**
	 * Create the mock objects for testing.
	 */
	@BeforeEach
	void setupMocks() throws Exception {
		this.jndiContext = mock(Context.class);
		this.connectionFactory = mock(ConnectionFactory.class);
		this.connection = mock(Connection.class);
		this.session = mock(Session.class);
		this.queue = mock(Queue.class);

		given(this.connectionFactory.createConnection()).willReturn(this.connection);
		given(this.connection.createSession(useTransactedTemplate(), Session.AUTO_ACKNOWLEDGE)).willReturn(this.session);
		given(this.session.getTransacted()).willReturn(useTransactedSession());
		given(this.jndiContext.lookup("testDestination")).willReturn(this.queue);
	}

	private JmsTemplate createTemplate() {
		JmsTemplate template = new JmsTemplate();
		JndiDestinationResolver destMan = new JndiDestinationResolver();
		destMan.setJndiTemplate(new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return JmsTemplateTests.this.jndiContext;
			}
		});
		template.setDestinationResolver(destMan);
		template.setSessionTransacted(useTransactedTemplate());
		return template;
	}

	protected boolean useTransactedSession() {
		return false;
	}

	protected boolean useTransactedTemplate() {
		return false;
	}

	protected Session getLocalSession() {
		return this.session;
	}


	@Test
	void testExceptionStackTrace() {
		JMSException jmsEx = new JMSException("could not connect");
		Exception innerEx = new Exception("host not found");
		jmsEx.setLinkedException(innerEx);
		JmsException springJmsEx = JmsUtils.convertJmsAccessException(jmsEx);
		StringWriter sw = new StringWriter();
		PrintWriter out = new PrintWriter(sw);
		springJmsEx.printStackTrace(out);
		String trace = sw.toString();
		assertThat(trace.indexOf("host not found") > 0).as("inner jms exception not found").isTrue();
	}

	@Test
	void testProducerCallback() throws Exception {
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);

		MessageProducer messageProducer = mock(MessageProducer.class);
		given(this.session.createProducer(null)).willReturn(messageProducer);
		given(messageProducer.getPriority()).willReturn(4);

		template.execute((ProducerCallback<Void>) (session1, producer) -> {
			session1.getTransacted();
			producer.getPriority();
			return null;
		});

		verify(messageProducer).close();
		verify(this.session).close();
		verify(this.connection).close();
	}

	@Test
	void testProducerCallbackWithIdAndTimestampDisabled() throws Exception {
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);
		template.setMessageIdEnabled(false);
		template.setMessageTimestampEnabled(false);

		MessageProducer messageProducer = mock(MessageProducer.class);
		given(this.session.createProducer(null)).willReturn(messageProducer);
		given(messageProducer.getPriority()).willReturn(4);

		template.execute((ProducerCallback<Void>) (session1, producer) -> {
			session1.getTransacted();
			producer.getPriority();
			return null;
		});

		verify(messageProducer).setDisableMessageID(true);
		verify(messageProducer).setDisableMessageTimestamp(true);
		verify(messageProducer).close();
		verify(this.session).close();
		verify(this.connection).close();
	}

	/**
	 * Test the method execute(SessionCallback action).
	 */
	@Test
	void testSessionCallback() throws Exception {
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);

		template.execute(new SessionCallback<Void>() {
			@Override
			public Void doInJms(Session session) throws JMSException {
				session.getTransacted();
				return null;
			}
		});

		verify(this.session).close();
		verify(this.connection).close();
	}

	@Test
	void testSessionCallbackWithinSynchronizedTransaction() throws Exception {
		SingleConnectionFactory scf = new SingleConnectionFactory(this.connectionFactory);
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(scf);

		TransactionSynchronizationManager.initSynchronization();
		try {
			template.execute(new SessionCallback<Void>() {
				@Override
				public Void doInJms(Session session) throws JMSException {
					session.getTransacted();
					return null;
				}
			});
			template.execute(new SessionCallback<Void>() {
				@Override
				public Void doInJms(Session session) throws JMSException {
					session.getTransacted();
					return null;
				}
			});

			assertThat(ConnectionFactoryUtils.getTransactionalSession(scf, null, false)).isSameAs(this.session);
			assertThat(ConnectionFactoryUtils.getTransactionalSession(scf, scf.createConnection(), false)).isSameAs(this.session);

			TransactionAwareConnectionFactoryProxy tacf = new TransactionAwareConnectionFactoryProxy(scf);
			Connection tac = tacf.createConnection();
			Session tas = tac.createSession(false, Session.AUTO_ACKNOWLEDGE);
			tas.getTransacted();
			tas.close();
			tac.close();

			List<TransactionSynchronization> synchs = TransactionSynchronizationManager.getSynchronizations();
			assertThat(synchs.size()).isEqualTo(1);
			TransactionSynchronization synch = synchs.get(0);
			synch.beforeCommit(false);
			synch.beforeCompletion();
			synch.afterCommit();
			synch.afterCompletion(TransactionSynchronization.STATUS_UNKNOWN);
		}
		finally {
			TransactionSynchronizationManager.clearSynchronization();
			scf.destroy();
		}
		assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty()).isTrue();

		verify(this.connection).start();
		if (useTransactedTemplate()) {
			verify(this.session).commit();
		}
		verify(this.session).close();
		verify(this.connection).stop();
		verify(this.connection).close();
	}

	/**
	 * Test sending to a destination using the method
	 * send(Destination d, MessageCreator messageCreator)
	 */
	@Test
	void testSendDestination() throws Exception {
		doTestSendDestination(true, false, true, false);
	}

	/**
	 * Test sending to a destination using the method
	 * send(String d, MessageCreator messageCreator)
	 */
	@Test
	void testSendDestinationName() throws Exception {
		doTestSendDestination(false, false, true, false);
	}

	/**
	 * Test sending to a destination using the method
	 * send(Destination d, MessageCreator messageCreator) using QOS parameters.
	 */
	@Test
	void testSendDestinationWithQOS() throws Exception {
		doTestSendDestination(true, false, false, true);
	}

	/**
	 * Test sending to a destination using the method
	 * send(String d, MessageCreator messageCreator) using QOS parameters.
	 */
	@Test
	void testSendDestinationNameWithQOS() throws Exception {
		doTestSendDestination(false, false, false, true);
	}

	/**
	 * Test sending to the default destination.
	 */
	@Test
	void testSendDefaultDestination() throws Exception {
		doTestSendDestination(true, true, true, true);
	}

	/**
	 * Test sending to the default destination name.
	 */
	@Test
	void testSendDefaultDestinationName() throws Exception {
		doTestSendDestination(false, true, true, true);
	}

	/**
	 * Test sending to the default destination using explicit QOS parameters.
	 */
	@Test
	void testSendDefaultDestinationWithQOS() throws Exception {
		doTestSendDestination(true, true, false, false);
	}

	/**
	 * Test sending to the default destination name using explicit QOS parameters.
	 */
	@Test
	void testSendDefaultDestinationNameWithQOS() throws Exception {
		doTestSendDestination(false, true, false, false);
	}

	/**
	 * Common method for testing a send method that uses the MessageCreator
	 * callback but with different QOS options.
	 * @param ignoreQOS test using default QOS options.
	 */
	private void doTestSendDestination(
			boolean explicitDestination, boolean useDefaultDestination,
			boolean ignoreQOS, boolean disableIdAndTimestamp) throws Exception {

		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);

		String destinationName = "testDestination";

		if (useDefaultDestination) {
			if (explicitDestination) {
				template.setDefaultDestination(this.queue);
			}
			else {
				template.setDefaultDestinationName(destinationName);
			}
		}
		if (disableIdAndTimestamp) {
			template.setMessageIdEnabled(false);
			template.setMessageTimestampEnabled(false);
		}

		MessageProducer messageProducer = mock(MessageProducer.class);
		TextMessage textMessage = mock(TextMessage.class);

		given(this.session.createProducer(this.queue)).willReturn(messageProducer);
		given(this.session.createTextMessage("just testing")).willReturn(textMessage);

		if (!ignoreQOS) {
			template.setQosSettings(this.qosSettings);
		}

		if (useDefaultDestination) {
			template.send(new MessageCreator() {
				@Override
				public Message createMessage(Session session) throws JMSException {
					return session.createTextMessage("just testing");
				}
			});
		}
		else {
			if (explicitDestination) {
				template.send(this.queue, new MessageCreator() {
					@Override
					public Message createMessage(Session session) throws JMSException {
						return session.createTextMessage("just testing");
					}
				});
			}
			else {
				template.send(destinationName, new MessageCreator() {
					@Override
					public Message createMessage(Session session) throws JMSException {
						return session.createTextMessage("just testing");
					}
				});
			}
		}

		if (useTransactedTemplate()) {
			verify(this.session).commit();
		}

		if (disableIdAndTimestamp) {
			verify(messageProducer).setDisableMessageID(true);
			verify(messageProducer).setDisableMessageTimestamp(true);
		}

		if (ignoreQOS) {
			verify(messageProducer).send(textMessage);
		}
		else {
			verify(messageProducer).send(textMessage, this.qosSettings.getDeliveryMode(),
					this.qosSettings.getPriority(), this.qosSettings.getTimeToLive());
		}
		verify(messageProducer).close();
		verify(this.session).close();
		verify(this.connection).close();
	}

	@Test
	void testConverter() throws Exception {
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);
		template.setMessageConverter(new SimpleMessageConverter());
		String s = "Hello world";

		MessageProducer messageProducer = mock(MessageProducer.class);
		TextMessage textMessage = mock(TextMessage.class);

		given(this.session.createProducer(this.queue)).willReturn(messageProducer);
		given(this.session.createTextMessage("Hello world")).willReturn(textMessage);

		template.convertAndSend(this.queue, s);

		verify(messageProducer).send(textMessage);
		verify(messageProducer).close();
		if (useTransactedTemplate()) {
			verify(this.session).commit();
		}
		verify(this.session).close();
		verify(this.connection).close();
	}

	@Test
	void testReceiveDefaultDestination() throws Exception {
		doTestReceive(true, true, false, false, false, false, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveDefaultDestinationName() throws Exception {
		doTestReceive(false, true, false, false, false, false, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveDestination() throws Exception {
		doTestReceive(true, false, false, false, false, true, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveDestinationWithClientAcknowledge() throws Exception {
		doTestReceive(true, false, false, true, false, false, 1000);
	}

	@Test
	void testReceiveDestinationName() throws Exception {
		doTestReceive(false, false, false, false, false, true, 1000);
	}

	@Test
	void testReceiveDefaultDestinationWithSelector() throws Exception {
		doTestReceive(true, true, false, false, true, true, 1000);
	}

	@Test
	void testReceiveDefaultDestinationNameWithSelector() throws Exception {
		doTestReceive(false, true, false, false, true, true, JmsTemplate.RECEIVE_TIMEOUT_NO_WAIT);
	}

	@Test
	void testReceiveDestinationWithSelector() throws Exception {
		doTestReceive(true, false, false, false, true, false, 1000);
	}

	@Test
	void testReceiveDestinationWithClientAcknowledgeWithSelector() throws Exception {
		doTestReceive(true, false, false, true, true, true, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveDestinationNameWithSelector() throws Exception {
		doTestReceive(false, false, false, false, true, false, JmsTemplate.RECEIVE_TIMEOUT_NO_WAIT);
	}

	@Test
	void testReceiveAndConvertDefaultDestination() throws Exception {
		doTestReceive(true, true, true, false, false, false, 1000);
	}

	@Test
	void testReceiveAndConvertDefaultDestinationName() throws Exception {
		doTestReceive(false, true, true, false, false, false, 1000);
	}

	@Test
	void testReceiveAndConvertDestinationName() throws Exception {
		doTestReceive(false, false, true, false, false, true, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveAndConvertDestination() throws Exception {
		doTestReceive(true, false, true, false, false, true, 1000);
	}

	@Test
	void testReceiveAndConvertDefaultDestinationWithSelector() throws Exception {
		doTestReceive(true, true, true, false, true, true, JmsTemplate.RECEIVE_TIMEOUT_NO_WAIT);
	}

	@Test
	void testReceiveAndConvertDestinationNameWithSelector() throws Exception {
		doTestReceive(false, false, true, false, true, true, JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT);
	}

	@Test
	void testReceiveAndConvertDestinationWithSelector() throws Exception {
		doTestReceive(true, false, true, false, true, false, 1000);
	}

	private void doTestReceive(
			boolean explicitDestination, boolean useDefaultDestination, boolean testConverter,
			boolean clientAcknowledge, boolean messageSelector, boolean noLocal, long timeout)
			throws Exception {

		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);

		String destinationName = "testDestination";

		if (useDefaultDestination) {
			if (explicitDestination) {
				template.setDefaultDestination(this.queue);
			}
			else {
				template.setDefaultDestinationName(destinationName);
			}
		}
		if (noLocal) {
			template.setPubSubNoLocal(true);
		}
		template.setReceiveTimeout(timeout);

		MessageConsumer messageConsumer = mock(MessageConsumer.class);

		String selectorString = "selector";
		given(this.session.createConsumer(this.queue,
				messageSelector ? selectorString : null)).willReturn(messageConsumer);

		if (!useTransactedTemplate() && !useTransactedSession()) {
			given(this.session.getAcknowledgeMode()).willReturn(
					clientAcknowledge ? Session.CLIENT_ACKNOWLEDGE
							: Session.AUTO_ACKNOWLEDGE);
		}

		TextMessage textMessage = mock(TextMessage.class);

		if (testConverter) {
			given(textMessage.getText()).willReturn("Hello World!");
		}

		if (timeout == JmsTemplate.RECEIVE_TIMEOUT_NO_WAIT) {
			given(messageConsumer.receiveNoWait()).willReturn(textMessage);
		}
		else if (timeout == JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT) {
			given(messageConsumer.receive()).willReturn(textMessage);
		}
		else {
			given(messageConsumer.receive(timeout)).willReturn(textMessage);
		}

		Message message = null;
		String textFromMessage = null;

		if (useDefaultDestination) {
			if (testConverter) {
				textFromMessage = (String)
						(messageSelector ? template.receiveSelectedAndConvert(selectorString) :
						template.receiveAndConvert());
			}
			else {
				message = (messageSelector ? template.receiveSelected(selectorString) : template.receive());
			}
		}
		else if (explicitDestination) {
			if (testConverter) {
				textFromMessage = (String)
						(messageSelector ? template.receiveSelectedAndConvert(this.queue, selectorString) :
						template.receiveAndConvert(this.queue));
			}
			else {
				message = (messageSelector ? template.receiveSelected(this.queue, selectorString) :
						template.receive(this.queue));
			}
		}
		else {
			if (testConverter) {
				textFromMessage = (String)
						(messageSelector ? template.receiveSelectedAndConvert(destinationName, selectorString) :
						template.receiveAndConvert(destinationName));
			}
			else {
				message = (messageSelector ? template.receiveSelected(destinationName, selectorString) :
						template.receive(destinationName));
			}
		}

		if (testConverter) {
			assertThat(textFromMessage).as("Message text should be equal").isEqualTo("Hello World!");
		}
		else {
			assertThat(textMessage).as("Messages should refer to the same object").isEqualTo(message);
		}

		verify(this.connection).start();
		verify(this.connection).close();
		if (useTransactedTemplate()) {
			verify(this.session).commit();
		}
		verify(this.session).close();
		if (!useTransactedSession() && clientAcknowledge) {
			verify(textMessage).acknowledge();
		}
		verify(messageConsumer).close();
	}

	@Test
	void testSendAndReceiveDefaultDestination() throws Exception {
		doTestSendAndReceive(true, true, 1000L);
	}

	@Test
	void testSendAndReceiveDefaultDestinationName() throws Exception {
		doTestSendAndReceive(false, true, 1000L);
	}

	@Test
	void testSendAndReceiveDestination() throws Exception {
		doTestSendAndReceive(true, false, 1000L);
	}

	@Test
	void testSendAndReceiveDestinationName() throws Exception {
		doTestSendAndReceive(false, false, 1000L);
	}

	private void doTestSendAndReceive(boolean explicitDestination, boolean useDefaultDestination, long timeout)
			throws Exception {

		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);

		String destinationName = "testDestination";
		if (useDefaultDestination) {
			if (explicitDestination) {
				template.setDefaultDestination(this.queue);
			}
			else {
				template.setDefaultDestinationName(destinationName);
			}
		}
		template.setReceiveTimeout(timeout);

		Session localSession = getLocalSession();
		TemporaryQueue replyDestination = mock(TemporaryQueue.class);
		MessageProducer messageProducer = mock(MessageProducer.class);
		given(localSession.createProducer(this.queue)).willReturn(messageProducer);
		given(localSession.createTemporaryQueue()).willReturn(replyDestination);

		MessageConsumer messageConsumer = mock(MessageConsumer.class);
		given(localSession.createConsumer(replyDestination)).willReturn(messageConsumer);


		TextMessage request = mock(TextMessage.class);
		MessageCreator messageCreator = mock(MessageCreator.class);
		given(messageCreator.createMessage(localSession)).willReturn(request);

		TextMessage reply = mock(TextMessage.class);
		if (timeout == JmsTemplate.RECEIVE_TIMEOUT_NO_WAIT) {
			given(messageConsumer.receiveNoWait()).willReturn(reply);
		}
		else if (timeout == JmsTemplate.RECEIVE_TIMEOUT_INDEFINITE_WAIT) {
			given(messageConsumer.receive()).willReturn(reply);
		}
		else {
			given(messageConsumer.receive(timeout)).willReturn(reply);
		}

		Message message = null;
		if (useDefaultDestination) {
			message = template.sendAndReceive(messageCreator);
		}
		else if (explicitDestination) {
			message = template.sendAndReceive(this.queue, messageCreator);
		}
		else {
			message = template.sendAndReceive(destinationName, messageCreator);
		}

		// replyTO set on the request
		verify(request).setJMSReplyTo(replyDestination);
		assertThat(message).as("Reply message not received").isSameAs(reply);
		verify(this.connection).start();
		verify(this.connection).close();
		verify(localSession).close();
		verify(messageConsumer).close();
		verify(messageProducer).close();
	}

	@Test
	void testIllegalStateException() throws Exception {
		doTestJmsException(new javax.jms.IllegalStateException(""), org.springframework.jms.IllegalStateException.class);
	}

	@Test
	void testInvalidClientIDException() throws Exception {
		doTestJmsException(new javax.jms.InvalidClientIDException(""), InvalidClientIDException.class);
	}

	@Test
	void testInvalidDestinationException() throws Exception {
		doTestJmsException(new javax.jms.InvalidDestinationException(""), InvalidDestinationException.class);
	}

	@Test
	void testInvalidSelectorException() throws Exception {
		doTestJmsException(new javax.jms.InvalidSelectorException(""), InvalidSelectorException.class);
	}

	@Test
	void testJmsSecurityException() throws Exception {
		doTestJmsException(new javax.jms.JMSSecurityException(""), JmsSecurityException.class);
	}

	@Test
	void testMessageEOFException() throws Exception {
		doTestJmsException(new javax.jms.MessageEOFException(""), MessageEOFException.class);
	}

	@Test
	void testMessageFormatException() throws Exception {
		doTestJmsException(new javax.jms.MessageFormatException(""), MessageFormatException.class);
	}

	@Test
	void testMessageNotReadableException() throws Exception {
		doTestJmsException(new javax.jms.MessageNotReadableException(""), MessageNotReadableException.class);
	}

	@Test
	void testMessageNotWriteableException() throws Exception {
		doTestJmsException(new javax.jms.MessageNotWriteableException(""), MessageNotWriteableException.class);
	}

	@Test
	void testResourceAllocationException() throws Exception {
		doTestJmsException(new javax.jms.ResourceAllocationException(""), ResourceAllocationException.class);
	}

	@Test
	void testTransactionInProgressException() throws Exception {
		doTestJmsException(new javax.jms.TransactionInProgressException(""), TransactionInProgressException.class);
	}

	@Test
	void testTransactionRolledBackException() throws Exception {
		doTestJmsException(new javax.jms.TransactionRolledBackException(""), TransactionRolledBackException.class);
	}

	@Test
	void testUncategorizedJmsException() throws Exception {
		doTestJmsException(new javax.jms.JMSException(""), UncategorizedJmsException.class);
	}

	protected void doTestJmsException(JMSException original, Class<? extends JmsException> thrownExceptionClass) throws Exception {
		JmsTemplate template = createTemplate();
		template.setConnectionFactory(this.connectionFactory);
		template.setMessageConverter(new SimpleMessageConverter());
		String s = "Hello world";

		MessageProducer messageProducer = mock(MessageProducer.class);
		TextMessage textMessage = mock(TextMessage.class);

		reset(this.session);
		given(this.session.createProducer(this.queue)).willReturn(messageProducer);
		given(this.session.createTextMessage("Hello world")).willReturn(textMessage);

		willThrow(original).given(messageProducer).send(textMessage);

		assertThatExceptionOfType(thrownExceptionClass).isThrownBy(() ->
				template.convertAndSend(this.queue, s))
			.withCause(original);

		verify(messageProducer).close();
		verify(this.session).close();
		verify(this.connection).close();
	}

}

 

TransactionTemplate

TransactionTemplate模板类,可简化程序化事务划分和事务异常处理。核心方法是execute(org.springframework.transaction.support.TransactionCallback <T>),它支持实现TransactionCallback接口的事务代码。该模板处理事务生命周期和可能的异常,这样,既不需要TransactionCallback实现也不需要调用代码来显式处理事务。

典型用法:允许编写使用诸如JDBC数据源之类的资源但本身不支持事务的低级数据访问对象。相反,他们可以隐式参与使用此类由高层应用程序服务处理的事务,并通过内部类回调对象对低层服务进行调用。

可以通过使用事务管理器引用的直接实例化在服务实现中使用,或者可以在应用程序上下文中进行准备并作为bean引用传递给服务。注意:事务管理器应始终在应用程序上下文中配置为bean:在第一种情况下,直接提供给服务,在第二种情况下,提供给准备好的模板。

支持按名称设置传播行为和隔离级别,以便在上下文定义中进行方便的配置。

类所在位置:

package org.springframework.transaction.support;

import java.lang.reflect.UndeclaredThrowableException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.Assert;

/**
 * Template class that simplifies programmatic transaction demarcation and
 * transaction exception handling.
 *
 * <p>The central method is {@link #execute}, supporting transactional code that
 * implements the {@link TransactionCallback} interface. This template handles
 * the transaction lifecycle and possible exceptions such that neither the
 * TransactionCallback implementation nor the calling code needs to explicitly
 * handle transactions.
 *
 * <p>Typical usage: Allows for writing low-level data access objects that use
 * resources such as JDBC DataSources but are not transaction-aware themselves.
 * Instead, they can implicitly participate in transactions handled by higher-level
 * application services utilizing this class, making calls to the low-level
 * services via an inner-class callback object.
 *
 * <p>Can be used within a service implementation via direct instantiation with
 * a transaction manager reference, or get prepared in an application context
 * and passed to services as bean reference. Note: The transaction manager should
 * always be configured as bean in the application context: in the first case given
 * to the service directly, in the second case given to the prepared template.
 *
 * <p>Supports setting the propagation behavior and the isolation level by name,
 * for convenient configuration in context definitions.
 *
 * @author Juergen Hoeller
 * @since 17.03.2003
 * @see #execute
 * @see #setTransactionManager
 * @see org.springframework.transaction.PlatformTransactionManager
 */
@SuppressWarnings("serial")
public class TransactionTemplate extends DefaultTransactionDefinition
		implements TransactionOperations, InitializingBean {

TransactionTemplate核心方法:

	@Override
	@Nullable
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		}
		else {
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try {
				result = action.doInTransaction(status);
			}
			catch (RuntimeException | Error ex) {
				// Transactional code threw application exception -> rollback
				rollbackOnException(status, ex);
				throw ex;
			}
			catch (Throwable ex) {
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			this.transactionManager.commit(status);
			return result;
		}
	}
  • 使用Spring预定义的DefaultTransactionDefinition ,然后通过TransactionManager的getTransaction()方法首先声明事务开始
  • 如果有异常发生,则使用TransactionManager的rollback()方法进行事务回滚
  • 如果成功,则执行TransactionManager的commit()方法进行事务提交

TransactionCallback功能接口定义:

package org.springframework.transaction.support;

import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionStatus;

/**
 * Callback interface for transactional code. Used with {@link TransactionTemplate}'s
 * {@code execute} method, often as anonymous class within a method implementation.
 *
 * <p>Typically used to assemble various calls to transaction-unaware data access
 * services into a higher-level service method with transaction demarcation. As an
 * alternative, consider the use of declarative transaction demarcation (e.g. through
 * Spring's {@link org.springframework.transaction.annotation.Transactional} annotation).
 *
 * @author Juergen Hoeller
 * @since 17.03.2003
 * @see TransactionTemplate
 * @see CallbackPreferringPlatformTransactionManager
 * @param <T> the result type
 */
@FunctionalInterface
public interface TransactionCallback<T> {

	/**
	 * Gets called by {@link TransactionTemplate#execute} within a transactional context.
	 * Does not need to care about transactions itself, although it can retrieve and
	 * influence the status of the current transaction via the given status object,
	 * e.g. setting rollback-only.
	 * <p>Allows for returning a result object created within the transaction, i.e. a
	 * domain object or a collection of domain objects. A RuntimeException thrown by the
	 * callback is treated as application exception that enforces a rollback. Any such
	 * exception will be propagated to the caller of the template, unless there is a
	 * problem rolling back, in which case a TransactionException will be thrown.
	 * @param status associated transaction status
	 * @return a result object, or {@code null}
	 * @see TransactionTemplate#execute
	 * @see CallbackPreferringPlatformTransactionManager#execute
	 */
	@Nullable
	T doInTransaction(TransactionStatus status);

}

编程式事务操作示例:

package org.springframework.transaction.support;

import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.transaction.testfixture.CallCountingTransactionManager;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

/**
 * @author Juergen Hoeller
 */
public class SimpleTransactionScopeTests {

	@Test
	@SuppressWarnings("resource")
	public void getFromScope() throws Exception {
		GenericApplicationContext context = new GenericApplicationContext();
		context.getBeanFactory().registerScope("tx", new SimpleTransactionScope());

		GenericBeanDefinition bd1 = new GenericBeanDefinition();
		bd1.setBeanClass(TestBean.class);
		bd1.setScope("tx");
		bd1.setPrimary(true);
		context.registerBeanDefinition("txScopedObject1", bd1);

		GenericBeanDefinition bd2 = new GenericBeanDefinition();
		bd2.setBeanClass(DerivedTestBean.class);
		bd2.setScope("tx");
		context.registerBeanDefinition("txScopedObject2", bd2);

		context.refresh();

		assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
				context.getBean(TestBean.class))
			.withCauseInstanceOf(IllegalStateException.class);

		assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
				context.getBean(DerivedTestBean.class))
			.withCauseInstanceOf(IllegalStateException.class);

		TestBean bean1 = null;
		DerivedTestBean bean2 = null;
		DerivedTestBean bean2a = null;
		DerivedTestBean bean2b = null;

		TransactionSynchronizationManager.initSynchronization();
		try {
			bean1 = context.getBean(TestBean.class);
			assertThat(context.getBean(TestBean.class)).isSameAs(bean1);

			bean2 = context.getBean(DerivedTestBean.class);
			assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2);
			context.getBeanFactory().destroyScopedBean("txScopedObject2");
			assertThat(TransactionSynchronizationManager.hasResource("txScopedObject2")).isFalse();
			assertThat(bean2.wasDestroyed()).isTrue();

			bean2a = context.getBean(DerivedTestBean.class);
			assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2a);
			assertThat(bean2a).isNotSameAs(bean2);
			context.getBeanFactory().getRegisteredScope("tx").remove("txScopedObject2");
			assertThat(TransactionSynchronizationManager.hasResource("txScopedObject2")).isFalse();
			assertThat(bean2a.wasDestroyed()).isFalse();

			bean2b = context.getBean(DerivedTestBean.class);
			assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2b);
			assertThat(bean2b).isNotSameAs(bean2);
			assertThat(bean2b).isNotSameAs(bean2a);
		}
		finally {
			TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
			TransactionSynchronizationManager.clearSynchronization();
		}

		assertThat(bean2a.wasDestroyed()).isFalse();
		assertThat(bean2b.wasDestroyed()).isTrue();
		assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty()).isTrue();

		assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
				context.getBean(TestBean.class))
			.withCauseInstanceOf(IllegalStateException.class);

		assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() ->
				context.getBean(DerivedTestBean.class))
			.withCauseInstanceOf(IllegalStateException.class);
	}

	@Test
	public void getWithTransactionManager() throws Exception {
		try (GenericApplicationContext context = new GenericApplicationContext()) {
			context.getBeanFactory().registerScope("tx", new SimpleTransactionScope());

			GenericBeanDefinition bd1 = new GenericBeanDefinition();
			bd1.setBeanClass(TestBean.class);
			bd1.setScope("tx");
			bd1.setPrimary(true);
			context.registerBeanDefinition("txScopedObject1", bd1);

			GenericBeanDefinition bd2 = new GenericBeanDefinition();
			bd2.setBeanClass(DerivedTestBean.class);
			bd2.setScope("tx");
			context.registerBeanDefinition("txScopedObject2", bd2);

			context.refresh();

			CallCountingTransactionManager tm = new CallCountingTransactionManager();
			TransactionTemplate tt = new TransactionTemplate(tm);
			Set<DerivedTestBean> finallyDestroy = new HashSet<>();

			tt.execute(status -> {
				TestBean bean1 = context.getBean(TestBean.class);
				assertThat(context.getBean(TestBean.class)).isSameAs(bean1);

				DerivedTestBean bean2 = context.getBean(DerivedTestBean.class);
				assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2);
				context.getBeanFactory().destroyScopedBean("txScopedObject2");
				assertThat(TransactionSynchronizationManager.hasResource("txScopedObject2")).isFalse();
				assertThat(bean2.wasDestroyed()).isTrue();

				DerivedTestBean bean2a = context.getBean(DerivedTestBean.class);
				assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2a);
				assertThat(bean2a).isNotSameAs(bean2);
				context.getBeanFactory().getRegisteredScope("tx").remove("txScopedObject2");
				assertThat(TransactionSynchronizationManager.hasResource("txScopedObject2")).isFalse();
				assertThat(bean2a.wasDestroyed()).isFalse();

				DerivedTestBean bean2b = context.getBean(DerivedTestBean.class);
				finallyDestroy.add(bean2b);
				assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2b);
				assertThat(bean2b).isNotSameAs(bean2);
				assertThat(bean2b).isNotSameAs(bean2a);

				Set<DerivedTestBean> immediatelyDestroy = new HashSet<>();
				TransactionTemplate tt2 = new TransactionTemplate(tm);
				tt2.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
				tt2.execute(status2 -> {
					DerivedTestBean bean2c = context.getBean(DerivedTestBean.class);
					immediatelyDestroy.add(bean2c);
					assertThat(context.getBean(DerivedTestBean.class)).isSameAs(bean2c);
					assertThat(bean2c).isNotSameAs(bean2);
					assertThat(bean2c).isNotSameAs(bean2a);
					assertThat(bean2c).isNotSameAs(bean2b);
					return null;
				});
				assertThat(immediatelyDestroy.iterator().next().wasDestroyed()).isTrue();
				assertThat(bean2b.wasDestroyed()).isFalse();

				return null;
			});

			assertThat(finallyDestroy.iterator().next().wasDestroyed()).isTrue();
		}
	}

}

JndiTemplate

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。两者之间的关键差别是目录服务中对象不但可以有名称还可以有属性(例如,用户有email地址),而命名服务中对象没有属性。

利用JNDI的命名与服务功能来满足企业级API对命名与服务的访问,诸如EJB、JMS、JDBC 2.0以及IIOP上的RMI通过JNDI来使用CORBA的命名服务。

JNDI架构提供了一组标准的独立于命名系统的API,这些API构建在与命名系统有关的驱动之上。这一层有助于将应用与实际数据源分离,因此不管应用访问的是LDAPRMIDNS、还是其他的目录服务。换句话说,JNDI独立于目录服务的具体实现,只要有目录的服务提供接口(或驱动),就可以使用目录。

关于JNDI要注意的重要一点是,它提供了应用编程接口(application programming interface,API)和服务提供者接口(service provider interface,SPI)。这一点的真正含义是,要让应用与命名服务或目录服务交互,必须有这个服务的JNDI服务提供者,这正是JNDI SPI发挥作用的地方。服务提供者基本上是一组类,这些类为各种具体的命名和目录服务实现了JNDI接口—很像JDBC驱动为各种具体的数据库系统实现了JDBC接口一样。作为一个应用开发者,我们不必操心JNDI SPI的具体实现。只需要确认要使用的每一个命名或目录服务都有服务提供者。

JNDI可访问的现有的目录及服务有:DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。

JndiTemplate简化JNDI操作的Helper类。 它提供了查找和绑定对象的方法,并允许JndiCallback接口的实现通过提供的JNDI命名上下文执行他们喜欢的任何操作。核心使用方法是:bind(),rebind(),unbind,lookup()。

类所在位置:

package org.springframework.jndi;

import java.util.Hashtable;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

/**
 * Helper class that simplifies JNDI operations. It provides methods to lookup and
 * bind objects, and allows implementations of the {@link JndiCallback} interface
 * to perform any operation they like with a JNDI naming context provided.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see JndiCallback
 * @see #execute
 */
public class JndiTemplate {

核心执行方法:

/**
	 * Execute the given JNDI context callback implementation.
	 * @param contextCallback the JndiCallback implementation to use
	 * @return a result object returned by the callback, or {@code null}
	 * @throws NamingException thrown by the callback implementation
	 * @see #createInitialContext
	 */
	@Nullable
	public <T> T execute(JndiCallback<T> contextCallback) throws NamingException {
		Context ctx = getContext();
		try {
			return contextCallback.doInContext(ctx);
		}
		finally {
			releaseContext(ctx);
		}
	}

JndiCallback回调执行功能性接口定义:

package org.springframework.jndi;

import javax.naming.Context;
import javax.naming.NamingException;

import org.springframework.lang.Nullable;

/**
 * Callback interface to be implemented by classes that need to perform an
 * operation (such as a lookup) in a JNDI context. This callback approach
 * is valuable in simplifying error handling, which is performed by the
 * JndiTemplate class. This is a similar to JdbcTemplate's approach.
 *
 * <p>Note that there is hardly any need to implement this callback
 * interface, as JndiTemplate provides all usual JNDI operations via
 * convenience methods.
 *
 * @author Rod Johnson
 * @param <T> the resulting object type
 * @see JndiTemplate
 * @see org.springframework.jdbc.core.JdbcTemplate
 */
@FunctionalInterface
public interface JndiCallback<T> {

	/**
	 * Do something with the given JNDI context.
	 * <p>Implementations don't need to worry about error handling
	 * or cleanup, as the JndiTemplate class will handle this.
	 * @param ctx the current JNDI context
	 * @throws NamingException if thrown by JNDI methods
	 * @return a result object, or {@code null}
	 */
	@Nullable
	T doInContext(Context ctx) throws NamingException;

}

 测试示例:

/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.jndi;

import javax.naming.Context;
import javax.naming.NameNotFoundException;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 08.07.2003
 */
public class JndiTemplateTests {

	@Test
	public void testLookupSucceeds() throws Exception {
		Object o = new Object();
		String name = "foo";
		final Context context = mock(Context.class);
		given(context.lookup(name)).willReturn(o);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		Object o2 = jt.lookup(name);
		assertThat(o2).isEqualTo(o);
		verify(context).close();
	}

	@Test
	public void testLookupFails() throws Exception {
		NameNotFoundException ne = new NameNotFoundException();
		String name = "foo";
		final Context context = mock(Context.class);
		given(context.lookup(name)).willThrow(ne);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		assertThatExceptionOfType(NameNotFoundException.class).isThrownBy(() ->
				jt.lookup(name));
		verify(context).close();
	}

	@Test
	public void testLookupReturnsNull() throws Exception {
		String name = "foo";
		final Context context = mock(Context.class);
		given(context.lookup(name)).willReturn(null);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		assertThatExceptionOfType(NameNotFoundException.class).isThrownBy(() ->
				jt.lookup(name));
		verify(context).close();
	}

	@Test
	public void testLookupFailsWithTypeMismatch() throws Exception {
		Object o = new Object();
		String name = "foo";
		final Context context = mock(Context.class);
		given(context.lookup(name)).willReturn(o);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		assertThatExceptionOfType(TypeMismatchNamingException.class).isThrownBy(() ->
				jt.lookup(name, String.class));
		verify(context).close();
	}

	@Test
	public void testBind() throws Exception {
		Object o = new Object();
		String name = "foo";
		final Context context = mock(Context.class);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		jt.bind(name, o);
		verify(context).bind(name, o);
		verify(context).close();
	}

	@Test
	public void testRebind() throws Exception {
		Object o = new Object();
		String name = "foo";
		final Context context = mock(Context.class);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		jt.rebind(name, o);
		verify(context).rebind(name, o);
		verify(context).close();
	}

	@Test
	public void testUnbind() throws Exception {
		String name = "something";
		final Context context = mock(Context.class);

		JndiTemplate jt = new JndiTemplate() {
			@Override
			protected Context createInitialContext() {
				return context;
			}
		};

		jt.unbind(name);
		verify(context).unbind(name);
		verify(context).close();
	}

}

参考文章

模板模式

深入理解 Spring 中的 ThreadPoolTaskExecutor 与 ListenableFuture 对象

ListenableFuture异步多线程代码实现

ListenableFuture、CompletableFuture、RxJava(Observable)区别

 类似资料: