First, the message asynchronous processing
Similar to remote method calls such as RMI, Hessian, burlap, and so on, they are all synchronous, so-called synchronous calls that the client must wait for the operation to complete, and if the remote service does not return any responses, the client waits until the service finishes.
Asynchronous invocations are different, and the client sends out the message without waiting for the service to finish to return immediately, just as the message is processed successfully as soon as it is sent out.
1.1 Sending of messages
In the world of asynchronous processing, we can compare the sending of messages to a Post Office system . For example, we want to send a letter to someone, we just have to prepare the letter, put it into the Post Office mailbox, we do not care about how the mail sent out, can arrive, the post Office system will ensure that the letter is finally delivered to the recipient of our hope. Similar to the Post Office system, when an application sends a message to another application, there is no direct association between the two apps, but the application that sends the message gives the message to a message system, which ensures that the message is delivered to the application receiving the message.
There are two important roles in the asynchronous messaging system: message Broker and destination. When an app sends a message, it will send it directly to the message broker, and the message broker will act as the Post office, ensuring that the message is delivered to a specific destination. When we mail the letter, the address of the letter is particularly important, the address in the message system is destination. However, unlike the address in the letter, destination is not defined by who the recipient is, but rather where the message is placed in the message broker (specifically, queue or topic), destination is more like a mailbox in the Post Office system.
Despite the existence of a variety of message systems, each message system has its own message routing, but there are two types of destination:queue and topic in general, each associated with a particular message processing model: Point- to-point ( Point-to-point/queue) and Publish/Subscribe (publish/subscribe/topic)
1.1.1 point-to-point (point-to-point) mode
In a point-to-point model, each message has only one sender and one recipient. As shown in the following:
In a point-to-point model, message broker places messages into a queue. When a recipient requests the next message, the message is taken out of the queue and passed to the receiver. Because messages are removed from the queue, this guarantees that a message can have only one recipient.
Although there is only one recipient for each message in the message queue, this does not mean that only one recipient can get the message from the queue, and that there are more than one recipient getting the message from the queue, except that they can only process the messages they receive. In fact, it is like queuing in a bank, the queue of people can be seen as a message, and the Bank work window is the recipient of the message, each window after the service of a customer will let the queue "next" to the window to transact business.
Also, if multiple receivers listen to a queue, it is difficult to determine which recipient is dealing with which message. But this is not necessarily bad, because it makes it very convenient for us to expand the application processing ability by increasing the receiver.
1.1.2 Publish/Subscribe (pub/subscribe) mode
In the Publish/subscribe mode, messages are sent to topic. Like a queue, many receivers can listen to the same topic, but unlike the queue each message is passed only to one recipient, all recipients who subscribe to the same topic receive a copy of the message, as shown in:
As we can see from the name of the Publish/subscribe, the Publisher publishes a message that all Subscribers can receive, which is the biggest feature of the publish subscription pattern. For the publisher, it only knows to publish the message to a specific topic, it does not care who listens to the topic, which means that it does not know how the messages are handled.
1.2 Benefits of the asynchronous messaging system
Before specifying the benefits of the asynchronous messaging system, let's look at the limitations of the synchronization system:
- A synchronous session means waiting: When a client calls a method of a remote service, the clients must wait for the remote method to finish before continuing, and if the client communicates with the remote service frequently or the remote service responds too slowly, it can affect the performance of the client
- Client and Service interface coupling: If the service interface changes, all clients need to be modified
- Client and service location coupling: The client must configure the service's address to use the remote service, and if the network topology changes, the client needs to reconfigure the service address
- Client and service availability coupling: If the service is not available, it can also cause the client to be unavailable
Let's look at how the asynchronous messaging system solves these problems.
No waiting
When a message is sent asynchronously, the client does not need to wait for it to finish processing. The client throws the message directly to the broker and does other things, and the broker is responsible for sending the message to the appropriate destination.
Because the client does not need to wait, the performance of the client can be greatly improved.
Message-oriented and decoupled
Unlike traditional RPC sessions based on method calls, message asynchronous sending is data-centric. This means that the client does not need to bind to a method signature, and any queue or topic Subscriber can handle the messages sent by the client. The client no longer cares about any related issues with the service party.
Location Independent
The call to the synchronous RPC service is located through the network address, which means that the client cannot get rid of the changes in the network topology. If the IP or port of the service changes, the client also needs to make corresponding changes.
Conversely, the client in the asynchronous messaging system does not care about where the service resides and how it processes the message, it is only responsible for sending the message to a specific queue or topic. So it doesn't matter where the services are located, as long as they can get the message from the queue or topic.
In the point-to-point mode, it is convenient to create a service cluster using the location-independent feature. The client does not need to care about the location of the service, the cluster of services only need to know the location of the broker, and get messages from the same queue, if the service pressure is too large to process the message in a timely manner, we only need to add a service instance in the cluster to listen to the same queue.
Location independence also plays an important role in the Publish/subscribe model. Multiple services can subscribe to the same topic, and they all get to each message in the topic, but the individual services can be handled differently. For example, we have a service collection that subscribes to a topic that receives new employee messages, so these services can get each new employee message, a service that adds new employees to the payroll system, another service that adds new employees to the HR system, and a service that empowers new employees with various system privileges, etc. Each subscription to a topic service can handle its own messages.
Reliability Assurance
When a client and service interact synchronously, any problem with the service hangs, which can affect the client's functioning. However, when the message is sent asynchronously, the client is separated from the service by broker, and the client is only responsible for sending the message, even if the service hangs when the message is sent, and the message is stored by the broker until the service is available and then processed.
Ii. sending messages via JMS
The Java message Service is a Java standard that defines a set of common APIs that interact with message broker. Before the advent of JMS, each message broker had its own unique set of APIs that prevented application code from being applied between different brokers. But with JMS, all code that interacts with broker can apply a common set of APIs, just like JDBC.
Of course, spring also provides support for JMS, which is jmstemplate. With Jmstemplate, we can more easily send and receive messages to queue and topic. We'll describe spring's implementation of JMS in more detail, but before we send and receive the message, we need a broker to be present.
2.1 Configuring Message Broker in Spring
ACTIVEMQ is a very good JMS framework, about ACTIVEMQ related content here do not do more introduction, specific reference: http://activemq.apache.org/, this article mainly describes how to configure and use it in spring.
2.1.1 Create a
Connection Factory
To send a message to ACTIVEMQ, we need to create a connection to it, which ActiveMQConnectionFactory
is the factory class in JMS that is responsible for creating the ACTIVEMQ connection. The following are configured in spring:
<bean id="connectionFactory"class="org.apache.activemq.spring.ActiveMQConnectionFactory"p:brokerURL="tcp://localhost:61616"/>
In addition, spring provides a dedicated namespace for ACTIVEMQ, and we can use the spring Activemq namespace to create a connection factory. First, declare the AMQ namespace in the configuration file:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:jms="http://www.springframework.org/schema/jms"xmlns:amq="http://activemq.apache.org/schema/core"xsi:schemaLocation="http://activemq.apache.org/schema/corehttp://activemq.apache.org/schema/core/activemq-core.xsdhttp://www.springframework.org/schema/jmshttp://www.springframework.org/schema/jms/spring-jms.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd">...</beans>
Then we can use the <amq:connectionFactory>
element to declare a connection factory:
<amq:connectionFactory id="connectionFactory"brokerURL="tcp://localhost:61616"/>
It is important to note that <amq:connectionFactory>
the elements are specifically targeted at ACTIVEMQ. If we are using other brokers, we need to use a different tag element or inject another factory bean. The above element brokerURL
specifies the IP and port of ACTIVEMQ in the server, and the above port value is the ACTIVEMQ default port.
2.1.2 declares ACTIVEMQ's message destination
In addition to having a connection factory, we also need to know the destination that the message is sent to. As mentioned above, there are only two classes of queue or topic in the destination of the message, and in spring we need to configure the corresponding Bean for queue or topic.
Configure a activemq queue bean:
<bean id="queue"class="org.apache.activemq.command.ActiveMQQueue"c:_="biz1.queue" />
Configure a activemq topic bean:
<bean id="topic"class="org.apache.activemq.command.ActiveMQTopic"c:_="biz1.topic" />
In the example above c:_
, the attribute represents the constructor parameter, which specifies the name of the queue or topic.
Like a connection factory, Spring provides another way to configure destination by using the spring ACTIVEMQ namespace.
To <amq:queue>
configure a queue with elements:
<amq:queue id="spittleQueue" physicalName="spittle.alert.queue" />
To <amq:topic>
configure a topic with an element:
<amq:topic id="spittleTopic" physicalName="biz1.topic" />
The properties in the above element represent the name of the physicalName
message channel, the name of queue and topic.
With the configuration of the above two components, we can send and receive messages to ACTIVEMQ. Send and receive messages we are using spring-provided jmstempate, which is spring's abstraction of JMS, which describes in detail the use of jmstemplate.
2.2 Using the Spring JMS template
While JMS provides a common API for interacting with various brokers, it is not very convenient to use, so let's take a look at the code that interacts with the broker using the normal JMS API.
2.2.1 sends a message to the broker code via the normal JMS API:
ConnectionFactory cf =new ActiveMQConnectionFactory("tcp://localhost:61616");Connection conn = null;Session session = null;try { conn = cf.createConnection(); session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); Destination destination = new ActiveMQQueue("spitter.queue"); MessageProducer producer = session.createProducer(destination); TextMessage message = session.createTextMessage(); message.setText("Hello world!"); producer.send(message);} catch (JMSException e) { // handle exception?} finally { try { if (session != null) { session.close(); } if (conn != null) { conn.close(); } } catch (JMSException ex) { }}
As we can see in the code above, in order to send a "Hello world" message with more than 20 lines of code, like JDBC, most of our code is to do some repetitive preparations, such as getting a connection, creating a session, exception handling, and so on. The same is true of the code that receives the message, in JDBC, Spring provides a jdbctemplate to simplify the development of the JDBC Code, and spring also provides a way JmsTemplate
to simplify the development of JMS message processing.
2.2.2 Using jmstemplate
Jmstemplate is actually a layer of abstraction from spring to JMS, which encapsulates most of the code that creates connections, gets sessions, and sends incoming messages, allowing us to focus on sending and receiving messages. In addition, the JmsTemplate
exception is also a good encapsulation, the corresponding basic exception is JMSException
.
To use Jmstemplate, configure it as a bean in the spring configuration file:
<bean id="jmsTemplate"class="org.springframework.jms.core.JmsTemplate"c:_-ref="connectionFactory" />
Because the jmstemplate needs to be connected to the broker first, it relies on a connectionfactory.
Send Message
If we have a business that needs to send asynchronous messages, we define such a business interface first:
public interface MyMessageService { void sendMessage(String message);}
There is only one method in the above interface, which is to send a message.
We write the implementation of this interface, in this interface implementation, we are using the JmsTemplate
implementation of asynchronous message sending:
Import Org.springframework.beans.factory.annotation.autowired;import Org.springframework.jms.core.jmsoperations;import Org.springframework.jms.core.messagecreator;import Org.springframework.stereotype.component;import Javax.jms.jmsexception;import Javax.jms.Message;import javax.jms.session;/** * Created by [email protected] on 2016/6/17. */@Componentpublic class Mymessageserviceimpl implements mymessageservice{@Autowired private jmsoperations Jmsopera tions; public void SendMessage (final String message) {jmsoperations.send ("Biz1.queue", new Messagecreator () { Public Message CreateMessage (session session) throws JMSException {return Session.createtextmessage (messag e); } }); }}
We can see that an object is injected into the implementation of our business JmsOperations
, and this object is JmsTempate
the implementation. JmsOperations
the send()
method has two parameters, the first one is the message destination
, the second is specific Message
, in the example above, the message is constructed by means of an anonymous inner class MessageCreator
createMessage()
.
As we can see from the above example, JmsTempate
we only need to care about sending the message, all the connections and the maintenance of the session are JmsTempate
responsible.
Set Default destination
In most cases, the destination of a business message is the same, so we don't have to fill in the destination every time we send it, we can configure it in a configuration file:
<bean id="jmsTemplate"class="org.springframework.jms.core.JmsTemplate"c:_-ref="connectionFactory"p:defaultDestinationName="biz1.queue" />
In the configuration above, our default destination value is biz1.queue
, because it simply declares a name and does not indicate what type of destination it is, so if a queue or topic with the same name exists, it will automatically match, if not present, A queue of the same name is created by default. If we want to specify the type of destination, we can configure it to rely on the previously configured destination bean:
<bean id="jmsTemplate"class="org.springframework.jms.core.JmsTemplate"c:_-ref="connectionFactory"p:defaultDestination-ref="biz1.Topic" />
When we configure the default destination, we can omit the first parameter when sending a message:
jmsOperations.send( new MessageCreator() { ... });
In fact, the above send()
method can be made simpler, we can use the message converter.
Sending messages using a message converter
In addition send()
to methods, JmsTemplate
methods are provided convertAndSend()
. The method needs to be send()
dependent on a MessageCreator
different convertAndSend()
method by simply passing in the message you want to send. Let convertAndSend()
's implement the method in the interface sendMessage()
:
public void sendMessage(final String message) { jmsOperations.convertAndSend(message);}
convertAndSend()
The method automatically converts the message you send to the Message
implementation of the specific conversion org.springframework.messaging.converter.MessageConverter
. Let's look at the MessageConverter
interface first:
public interface MessageConverter { Object fromMessage(Message<?> var1, Class<?> var2); Message<?> toMessage(Object var1, MessageHeaders var2);}
We can see that there are only two methods in this interface and are easy to implement. In fact, in most cases we don't need to implement this interface ourselves, Spring has given us a lot of common implementations:
By default, when JmsTemplate
the convertAndSend()
method uses the SimpleMessageConverter
. But we can also inject our custom attributes into it by configuration MessageConverter
JmsTemplate
, for example we have one MessageConverter
implementation bean:
<bean id="messageConverter"class="org.springframework.jms.support.converter.MappingJacksonMessageConverter" />
We can inject the above bean into the jmstemplate:
<bean id="jmsTemplate"class="org.springframework.jms.core.JmsTemplate"c:_-ref="connectionFactory"p:defaultDestinationName="spittle.alert.queue"p:messageConverter-ref="messageConverter" />
Consumer News
For consumption, JmsTemplate
it is easier to use than to send, just call JmsOperations
the receive()
method:
public class ReceiveMessage { @Autowired private JmsOperations jmsOperations; public String receive() { try { ObjectMessage message = (ObjectMessage) jmsOperations.receive(); return (String) message.getObject(); } catch (JMSException e) { e.printStackTrace(); throw JmsUtils.convertJmsAccessException(e); } }}
When the method is called, jmsOperations.receive()
it tries to get the message from the broker, and if there is no message at this time, the receive()
method waits until a message is generated. In the previous example, when we send a message, the message is encapsulated ObjectMessage
, and we can convert it back when we get it ObjectMessage
.
One thing to note here is that when the message.getObject()
method is called JMSException
, it is thrown, and the exception belongs to the JMS API. JMSException
is a check exception that throws a variety of things in a JMS operation, JMSException
but before we use it we JmsTemplate
don't capture anything JMSException
because the JmsTemplate
inside has JMSException
converted to a non-checked spring itself JmsException
. In the above code message.getObject()
JmsTemplate
, we need to capture the method because it is called a method instead of the methods. JMSException
But according to Spring's design, we should try to reduce the check exception, so in the catch block we also converted to JMSException
non-inspection through the Jmsutils tool JmsException
.
Similarly, as with the delivery of a message, we can also use the Jmstemplate receiveAndConvert()
method to replace the receive()
method:
public String receive() { return (String)jmsOperations.receiveAndConvert();}
We see that because of the JmsTemplate
method used, we do not need to catch the JMSException
check exception again.
They are msTemplate
synchronous regardless of the receive()
consumption message used or the receiveAndConvert()
method. This means that the receiver needs to wait while the message arrives. Does that look a little strange? The message is sent asynchronously and is synchronized when the message is received.
This is why the following message is driving Pojo, so let's look at how to implement an asynchronous receive message.
2.3 Creating a message-driven Pojo
We already know that JmsTemplate
the method receive()
is a synchronous method, before the message arrives this method will hang until the message appears, and if so, our application may appear waiting for the message and cannot do other things. Why not let the application deal with other businesses first, and then inform the application when the message appears?
In EJB, message driven bean(MDB)
it is possible to implement asynchronous processing of messages. Spring refers to the implementation of EJB3 for MDB in this respect, but in spring we call it message-driven Pojo, which is message-driven POJO(MDP)
.
2.3.1 Creating a Message listener
To be notified when a message appears, you need a listener to listen to the queue or topic, which is called a message-driven Pojo, because the listener is message-driven, but because the listener itself is a normal Pojo object and does not need to rely on any interface:
public class MyMessageHandler { public void handleMessage(String message){ //具体的实现 }}
With this Pojo object, you only need to do a simple configuration.
2.3.2 Configuring message Listeners
The key to giving the above Pojo the ability to receive messages is to configure it as a spring message listener, and spring's JMS namespace provides all the relevant configurations.
First, we now need to declare the above Pojo object as a bean:
<bean id="myMessageHandler"class="com.heaven.springexamples.jms.MyMessageHandler" />
Second, turn the MessageHandler into a message-driven pojo that declares the bean as a listener:
<jms:listener-container connection-factory="connectionFactory"><jms:listener destination="biz1.queue"ref="myMessageHandler" method="handleMessage" /></jms:listener-container>
With the above configuration, there is a message listener in the message listening container. The message listener is a special bean that listens to JMS destination and listens for the arrival of messages. Once the message arrives, the message listener will accept the message and send it to all relevant listener. The following illustration shows the entire internal processing process:
To configure the listener and listener, we used two elements in the JMS namespace. <jms:listener-container>
is the parent element, which <jms:listener >
is a child element. <jms:listener-container>
rely on one connectionFactory
, so that its various <jms:listener >
can listen to the message. <jms:listener >
used to define the bean and method that specifically receives the message. According to the above configuration, the method is called when the message arrives at the queue MyMessageHandler
handleMessage
.
2.3.3 Another way to implement a MessageListener interface
It is important to note that we MessageHandler
can also implement an MessageListener
interface, so that you do not need to specify the method of message processing, MyMessageHandler
the onMessage()
method is automatically called. The MessageListener interface is defined as follows:
public interface MessageListener { void onMessage(Message var1);}
Let's write a simple implementation class:
public class MyMessageListener implements MessageListener{ public void onMessage(Message message) { //具体的实现 }}
You can then configure the listener directly (without having to configure the method property):
<jms:listener-container connection-factory="connectionFactory"><jms:listener destination="biz1.queue"ref="myMessageHandler" /></jms:listener-container>
Spring Consolidated JMS (message middleware)