Github

The previous article on the pitfalls of integrating JMS with Spring/Spring Boot covered some of these performance pitfalls when using Spring JMS components, and this article takes a look at the various components of Spring JMS, what they do, and how to use them correctly.

JmsTemplate

Send and receive messages (Receive is synchronous and blocks). Each JmsTemplate instance has its own configuration, such as: ConnectionFactory, sessionTransacted, Sessionacknowledge, deliveryMode, timeToLive, etc. So instead of one Singleton Bean eating all JMS operations, you need to provide different JmsTemplate Beans for different scenarios.

Here is the class diagram (which contains only some of the key attributes) :

ConnectionFactory

Spring provides two javax.mail, JMS ConnectionFactory implementation: SingleConnectionFactory and CachingConnectionFactory. They are actually a Wrapper used to cache things like Connection, Session, MessageProducer, and MessageConsumer.

In fact, JmsTemplate’s Javadoc says:

NOTE: The 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.

The need to optimize resource usage to improve performance is also mentioned in the Caching Messaging Resources section of Spring JMS documentation:

The standard API involves creating many intermediate objects. To send a message the following ‘API’ walk is performed


ConnectionFactory->Connection->Session->MessageProducer->send


Between the ConnectionFactory and the Send operation there are three intermediate objects that are created and destroyed. To optimise the resource usage and increase performance two implementations of
ConnectionFactory are provided.

Here is the class diagram (which contains only some of the key attributes) :

SingleConnectionFactory

SingleConnectionFactory as the name suggests, createConnection(..) is called no matter how many times. All return the same Connection instance. But it does not cache sessions, so createSession(…) is called once. A new instance will be created.

Can pass SingleConnectionFactoryTest for details.

So in most cases, SingleConnectionFactory is not recommended.

CachingConnectionFactory

CachingConnectionFactory inherits from SingleConnectionFactory and retains the ability to cache the same Connection instance. Caches for sessions, MessageProducer, and MessageConsumer have also been added.

CachingConnectionFactory internally maintains an Acknowledge Mode -> List

Map, sessionCacheSize actually refers to the size of List

, So a maximum of 4 * sessionCacheSize sessions will be cached (because JMS specifies four Acknowledge modes). The CachingConnectionFactory is not an Object Pool in nature, so it will not cause a block or return null if the actual number of sessions requested exceeds sessionCacheSize.

Each Session returned by CachingConnectionFactory has a Map of ConsumerCacheKey -> MessageConsumer and DestinationCacheKey -> MessageProducer inside. Used to cache MessageProducer and MessageConsumer.

You can use CachingConnectionFactory to learn more.

MessageListenerContainer

Spring JMS has a feature called MessageListenerContainer that, according to the official documentation:

A message listener container is used to receive messages from a JMS message queue and drive the MessageListener that is injected into it.

Mentioned above is a MessageListener javax.mail. JMS. MessageListener, first saw this thing feels a bit strange, Because the normal usage of the MessageListener should MessageConsumer. SetMessageListener ().

Because MessageListenerContainer inherits from SmartLifeCycle, MessageListenerContainer provides you with the ability to start a Connection or a session, and to close a session or a connection so that you don’t have to worry about recycling.

To introduce the following two implementations SimpleMessageListenerContainer and DefaultMessageListenerContainer.

Here is the class diagram (which contains only some of the key attributes) :

SimpleMessageListenerContainer

SimpleMessageListenerContainer use MessageConsumer. SetMessageListener () listening to news, it does not support (such as PlatformTransactionManager) participate in external affairs.

It can hold multiple instances of MessageConsumer. The code is as follows:

/ /... private int concurrentConsumers = 1; private Set<Session> sessions; private Set<MessageConsumer> consumers; / /... protected void initializeConsumers() throws JMSException { // Register Sessions and MessageConsumers. synchronized (this.consumersMonitor) { if (this.consumers == null) { this.sessions = new HashSet<Session>(this.concurrentConsumers); this.consumers = new HashSet<MessageConsumer>(this.concurrentConsumers); Connection con = getSharedConnection(); for (int i = 0; i < this.concurrentConsumers; i++) { Session session = createSession(con); MessageConsumer consumer = createListenerConsumer(session); this.sessions.add(session); this.consumers.add(consumer); }}}}Copy the code

Its processing messages in one of two ways: 1) the traditional MessageConsumer. SetMessageListener (); 2) Use Executor.

if (this.taskExecutor ! = null) { consumer.setMessageListener(new MessageListener() { @Override public void onMessage(final Message message) { taskExecutor.execute(new Runnable() { @Override public void run() { processMessage(message, session); }}); }}); } else { consumer.setMessageListener(new MessageListener() { @Override public void onMessage(Message message) { processMessage(message, session); }}); }Copy the code

DefaultMessageListenerContainer

DefaultMessageListenerContainer and SimpleMessageListenerContainer is different, it USES the MessageConsumer. The receive () to process the message, and support the XA transaction.

Because receive() is a synchronous, blocking method and does not perform as well as setMessageListener(), it relies heavily on TaskExecutor, which also provides dynamic scaling benefits.

Please be careful not to multithread topics, otherwise you will receive duplicate messages, see the official documentation for details.

Receiving messages asynchronously

There are jmstemplate.receive *() and messageconsumer.receive *() to receive messages asynchronously.

MessageListener & MessageListenerContainer

Wrap a MessageListener into a MessageListenerContainer to receive messages. For example, see the official Asynchronous reception-message-driven POJOs document

SessionAwareMessageListener

SessionAwareMessageListener is to provide similar to a MessageListener interface, Spring’s MessageListenerContainer supports this interface, usage and MessageListener.

MessageListenerAdapter

The MessageListenerAdapter is Spring provides another way receives the message asynchronously, it is more flexible MessageListener and SessionAwareMessageListener, because it USES reflection mechanism to put the message to you on the way to receive messages.

Method of use see the official document SessionAwareMessageListener interface.

@JmsListener

@jMSListener is another way to receive messages. See the official annotation-Driven Listener EndPoints document for how to use it.

@ JmsListener and MessageListener, SessionAwareMessageListener, the MessageListenerAdapter also need a Container, Users can @ JmsListener. ContainerFactory attribute to specify JmsListenerContainerFactory.

Spring provides two JmsListenerContainerFactory implementation:

  1. DefaultJmsListenerContainerFactoryTo produce DefaultMessageListenerContainer Spring provided the BootDefaultJmsListenerContainerFactoryConfigurerAs a configuration tool
  2. SimpleMessageListenerContainer SimpleJmsListenerContainerFactory, used for production

So in the use of @ JmsListener need to carefully choose the right JmsListenerContainerFactory, rather than using a global configuration.

conclusion

There are three points to note when using Spring JMS:

  1. Configure the appropriate ConnectionFactory Beans as required, including multiple ConnectionFactory Beans if necessary.
  2. JmsTemplate,’s MessageListenerContainer JmsListenerContainerFactory need different Bean configuration according to actual condition, avoid using a set of global.
  3. JmsTemplate,’s MessageListenerContainer JmsListenerContainerFactory choosing appropriate ConnectionFactory.
  4. Set an appropriate Executor/ Thread pool size to avoid large numbers of Thread blocks.

Below is a diagram of the various components.

The resources

  • Spring JMS
  • Spring JMS Listener Adapters
  • JMS Javadoc