Interpretation of Spring Jms Integration in the MOM Series

Source: Internet
Author: User

A few times ago, I made some extensions to Spring Jms implementation. I took this opportunity to systematically study Spring's support for JMS. I hope you will like it!

This article is intended to explain from two dimensions (programming API and package structure). I hope you can read this article and have a general understanding of Spring's work at the JMS layer. Of course, if you like to deduct details, you are welcome to raise your questions!

Part 1: programming API

First, let's take a look at JmsTemplate, which is the most frequently used in Spring,

From the inheritance relationship, let's look at the interface JmsOperations, which can basically be summarized into the following methods:

 

Conveniencemethods for sending messages

Conveniencemethods for sending auto-converted messages

Conveniencemethods for processing ing messages

Conveniencemethods for processing ing auto-converted messages

Conveniencemethods for browsing messages

 

However, note that the exception from throws is not the JMSException standard in JMS 1.1, but the JmsException that has been translated. At the same time, we can see that this interface

Fully follows the CQRS principle. An MQ is actually the Queue after Wrapper. The knowledge of data structure tells us that queue has two storage structures: Array and Queue list. Array is good at random reading, and vertex list is good at deleting update operations. Once the underlying structure of the vertex list is adopted, Brower is a big problem. Pay special attention to this.

 

Let's take a look at JmsDestinationAccessor, which inherits from JmsAccessor (which implements InitializingBean without explanation). Note that the DestinationResolver class in it is mainly resolved from a simple String type name to a specific Destination, the default Implementation of DynamicDestinationResolver is basically enough. For example, if you want to expand and parse it into a Location that zookeeper can recognize, you can consider implementing this class.

 

Well, it's finally JmsTemplate's turn. First paste a piece of Javadoc (here there are two places to know first)

This template uses a org. springframework. jms. support. destination. dynamicDestinationResolver and a 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.

Straightforward, no explanation ......

NOTE: The ConnectionFactory used with this template shocould 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 pooling plant is well justified. Spring only provides SingleConnectionFactory. As for pooling, the specific Broker is implemented by itself. For example, AMQ has a PooledConnectionFactory Based on the Commons pool class library in it.

 

OK. Next we will go deep into JmsTemplate to learn about several important methods:

/** * 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. * 

Use {@ code execute (SessionCallback)} for the general case. * Starting the JMS Connection is just necessary for processing ing 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 */public T execute (SessionCallback Action, boolean startConnection) throws JmsException {Assert. notNull (action, Callback object must not be null); Connection conToClose = null; Session sessionToClose = null; try {// obtain the Resouce bound to the current thread through the transaction synchronization manager, here is JmsResourceHolder Session sessionToUse = ConnectionFactoryUtils. doGetTransactionalSession (getConnectionFactory (), 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) {// note the magic here-exception translation throw convertJmsAccessException (ex);} finally {JmsUtils. closeSession (sessionToClose); ConnectionFactoryUtils. releaseConnection (conToClose, getConnectionFactory (), startConnection );}}

 

 

/** * Send the given JMS message. * @param session the JMS Session to operate on * @param destination the JMS Destination to send to * @param messageCreator callback to create a JMS Message * @throws JMSException if thrown by JMS API methods */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);}}

 

 

Public void convertAndSend (Destination destination, final Object message, final MessagePostProcessor postProcessor) throws JmsException {send (destination, new MessageCreator () {public Message createMessage (Session session) throws JMSException {Message msg = getRequiredMessageConverter (). toMessage (message, session); return postProcessor. postProcessMessage (msg); // note that this is not post-processing for message sending, but post-processing for message Converter (a Hook before message sending )}});}

 

 

/** * Actually receive a JMS message. * @param session the JMS Session to operate on * @param consumer the JMS MessageConsumer to receive with * @return the JMS Message received, or {@code null} if none * @throws JMSException if thrown by JMS API methods */protected Message doReceive(Session session, MessageConsumer consumer) throws JMSException {try {// Use transaction timeout (if available).long timeout = getReceiveTimeout();JmsResourceHolder resourceHolder =(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());if (resourceHolder != null && resourceHolder.hasTimeout()) {timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());}Message message = doReceive(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);}}

 

The key code has been commented out, so I will not go into details here. Even if I master these core methods, this class will be taken down.

Well, from the perspective of programming APIs, this is almost the same.

 

Part 2: Package Structure

 

Next, let's take a closer look at Spring's integration with Jms from the perspective of the package structure, such:


 

The org. springframework. jms package provides some runtime versions with exceptions to the JMS specification. Let's take a look at jms2's improvements in this regard and we will know that spring is already a pioneer in this regard.

Org. springframework. jms. the config package contains parsing the Jms schema. This is a very useful feature provided by spring for us. If the schema is used well, it can also achieve interface-Oriented Programming and excellent scalability. If you are interested in this aspect, read the tips here.

The org. springframework. jms. connection package contains some Connection-related tools (ConnectionFactoryUtils) and basic classes (JmsResourceHolder ). Here we will focus on JmsTransactionManager (extends?actplatformtransactionmanager, The doXXX method is very interesting). This class is also a core work class for JMS local transaction processing, as shown below:


 

Org. springframework. jms. the core package mainly contains some spring-encapsulated callback interfaces, such as BrowserCallback, MessageCreator, MessagePostProcessor, ProducerCallback, and SessionCallback. Of course, the JmsTemplate we analyzed earlier is also in this package.

Org. springframework. jms. core. the support package contains an abstract class JmsGatewaySupport, which is not used for the time being. It is built into the afterPropertiesSet method with an initGateway Method for custom operations (custominitialization behavior ).

Org. springframework. jms. listener and org. springframework. jms. listener. the adapter package, we should pay attention to it. Just now, the programming API mainly introduced the sending of messages and how to handle the message acceptance. It mainly looked at the classes in these two packages. The class diagram is as follows:

 

 

Let's first take a look at the core methods of SimpleMessageListenerContainer:

 

/** * Create a MessageConsumer for the given JMS Session, * registering a MessageListener for the specified listener. * @param session the JMS Session to work on * @return the MessageConsumer * @throws JMSException if thrown by JMS methods * @see #executeListener */protected MessageConsumer createListenerConsumer(final Session session) throws JMSException {Destination destination = getDestination();if (destination == null) {destination = resolveDestinationName(session, getDestinationName());}MessageConsumer consumer = createConsumer(session, destination);if (this.taskExecutor != null) {consumer.setMessageListener(new MessageListener() {public void onMessage(final Message message) {taskExecutor.execute(new Runnable() {public void run() {processMessage(message, session);}});}});}else {consumer.setMessageListener(new MessageListener() {public void onMessage(Message message) {processMessage(message, session);}});}return consumer;}
How are you doing? It is a simple scheduling algorithm and has no advanced features such as failed reconnection. What if you need these functions? OK. Now the DefaultMessageListenerContainer is available. A relatively rich Listener container is different from SimpleMessageListenerContainer. It uses AsyncMessageListenerInvoker to execute a looped MessageConsumer. receive () is called to receive messages. Note that the Executor here is SimpleAsyncTaskExecutor by default, which is clearly written in the document:
NOTE: This implementation does not reuse threads! Consider a thread-pooling TaskExecutor implementation instead, in particular for executing a large number of short-lived tasks.
Let's take a look at several important member variables in this class: concurrentConsumers and maxConcurrentConsumers. By setting the setConcurrency method, you can scale up number of consumers between the minimum number ofconsumers (concurrentConsumers) and the maximum number of consumers (maxConcurrentConsumers ). So how can a single consumption task consume messages? Here is another variable that requires attention, namely, idleTaskExecutionLimit. The official explanation is clear:

 

Within each task execution, a number of message reception attempts (according to the maxMessagesPerTask setting) will each wait for an incoming message (according to the receiveTimeout setting). If all of those receive attempts in a given task return without a message, the task is considered idle with respect to received messages. Such a task may still be rescheduled; however, once it reached the specified idleTaskExecutionLimit, it will shut down (in case of dynamic scaling).
Next, let's take a look at the most important scheduling method in this class. In its internal class AsyncMessageListenerInvoker, it is as follows:

 

 

public void run() {synchronized (lifecycleMonitor) {activeInvokerCount++;lifecycleMonitor.notifyAll();}boolean messageReceived = false;try {if (maxMessagesPerTask < 0) {messageReceived = executeOngoingLoop();}else {int messageCount = 0;while (isRunning() && messageCount < maxMessagesPerTask) {messageReceived = (invokeListener() || messageReceived);messageCount++;}}}catch (Throwable ex) {clearResources();if (!this.lastMessageSucceeded) {// We failed more than once in a row - sleep for recovery interval// even before first recovery attempt.sleepInbetweenRecoveryAttempts();}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.);}}}}}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 {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) {messageReceived = (invokeListener() || messageReceived);}}return messageReceived;}

This class is almost introduced here. Let's take a look at it ~

The org. springframework. jms. listener. endpoint package provides some JavaEE features-Support for JCA, which will not be expanded here.

Org. springframework. jms. support, org. springframework. jms. support. converter, org. springframework. jms. support. destination provides the Jms tool class JmsUtils (in my opinion, the JmsAccessor class can be considered in the core package, and some tool classes can be pulled here) for message converters (mainly including three types of conversions, support for Object <-> Message, XML <-> Message, Json <-> Message) and Destination is not difficult, so we will not discuss it here.

The org. springframework. jms. remoting package tells us that the underlying layer can use JMS for remote services, similar to RMI Remoting.

OK. This is almost the content. After reading so much, let's summarize the shortcomings of Spring in JMS encapsulation:

(1) Spring encapsulates JMS on the JMS 1.1 Specification (Deprecated is supported in 1.0.2). The support of JMS 2 has not been found in the latest version 4.0;

(2) No Hook method is reserved for sending and receiving messages. For example, we have this requirement-tracking the message trend. After the message is sent, we can write a bit of data to the local agent. The agent regularly and quantitatively pushes the data to the server for statistical operations and presentation. At this time, there is no out-of-box method to implement, of course there are many work ing methods, but it is not suitable for integration with open-source versions;

(3) Some fault tolerance policies are missing, such as message sending failure. How can this problem be solved?

 

If you have any questions, please leave a message to discuss them!

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.