As a native Java-enabled messaging middleware, ActiveMQ, Spring, which interrupts everywhere, also provides a custom solution that inherits the JMS connection ActiveMQ.Based on JMS native interface specification and native implementation, Spring self-encapsulates a set of solutions that can be well integrated into the Spring framework and managed effectively.For the time being, this article will only discuss ways to integrate JMS Middleware in basic Spring. For the integration of active EMQ in springboot, I intend to rewrite it in a subsequent article in the Springboot Beware series.
(This article is from the blog of happybks, oschina blogger: https://my.oschina.net/happyBKs/blog/1819114)
spring re-encapsulates several interfaces for JMS to replace the original interface when called:
(1) ConnectionFactory is used to manage connection factories for connections.
Note that it is not a ConnnectionFactory in the JMS specification
(2) Template class used by JMSTemplate to receive and invoke messages.
A template for receiving and sending messages.MessageProducer and MessageSageConsumer are used in the JMS specification.
(3) MessageListener message listener
ConnectionFactory
It is a connection pool that Spring provides us with.
Why do I need a connection pool?
That's because JMSTemplate creates a connection, session, and producer each time it sends a message.This is a very performance-intensive part, especially when you are working with large amounts of data.So spring provides us with a pool of connections.
In spring, spring provides two connection pools: SingleConnectionFactory and ChaingConnectionFactory.
SingleConnectionFactory requests to the same JMS server will only return the same Connection.
The CachingConnectionFactory inherits the SingleConnectionFactory and has all the functionality of the SingleConnectionFactory.At the same time, a new caching function has been added to cache Session sessions, producer s, consumer s.
JMSTemplate
JMSTemplate is provided by spring and can be used to easily manipulate JMS simply by registering this class with the spring container.
JMSTemplate is thread-safe and can be used throughout the application.But that doesn't mean we can only use one JMSTemplate.
MessageListener
Message Listener, we only need to implement one of its onMessage methods, which will receive a Message parameter.We can implement our processing of messages in the onMessage method.
Project Setup
We import dependencies:
First, we need to specify the base jar for the spring context.We also introduced an active core, which contains the underlying JMS specification interface; then we introduced spring activemq, which contains the JMS interface of spring that we just mentioned, as well as the implementation classes (ConnectionFactory and JMSTemplate of spring).
Note here that the active core already contains the spring context itself, since we have introduced the spring context in our project, in order to avoid conflicts, we need to remove the built-in spring context from the activemq core's coordinate dependency.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.happybks.jms</groupId> <artifactId>jms-spring</artifactId> <version>1.0-SNAPSHOT</version> <name>jms-spring</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.17.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jms --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>4.3.17.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.17.RELEASE</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-core --> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-core</artifactId> <version>5.7.0</version> <exclusions> <exclusion> <artifactId>spring-context</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency> </dependencies> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.20.1</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
spring JMS queue mode producer
Let's start by declaring a service interface:
package com.happybks.jms.producer; public interface ProducerService { void sendMessage(final String message); }
Then specify its implementation class:
Note the note here that if a bean in a bean container has a unique type, you can use @Autowired.
If two (or more) beans of the same type (or inheritance relationship) exist as expected, then it is not possible to specify which beans are by type because of the conflict exception, so @Resource(name=?) is required and by the ID of the beans.If you don't want to use @Autowired and want to indicate an id, you can use @Qualifier("???") with @Autowired.
package com.happybks.jms.producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator ; import javax.annotation.Resource; import javax.jms.*; public class ProducerServiceImpl implements ProducerService { @Autowired JmsTemplate jmsTemplate; //The reason @Resouce is used is that there may be multiple Destination objects in the project, where we need to inject by the id of the bean instead of @Autowired by type @Resource(name = "queueDestination") Destination destination; @Override public void sendMessage(final String message) { final MessageCreator messageCreator = new MessageCreator() { //Implementing the createMessage method of the MessageCreator interface requires the creation of messages.Creating a message requires a Session session. //Create a message @Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage(message); return textMessage; } }; //Send messages using jmsTemplate jmsTemplate.send(destination, messageCreator); System.out.println("Send a message:" + message); } }
Accordingly, we create a bean in the producer.xml configuration spring container.
I have IDE here using IDEA, you can choose to use spring Config when creating a new one
However, the declaration in the tag header of the beans for this generated xml has some problems and needs to be removed from the following two or the various configurations of the context namespace will not be prompted automatically.
The spring configuration file producer.xml is as follows:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!-- This is ActiveMQ For us ConnectionFactory --> <bean id="producerTargetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://127.0.0.1:61616"/> </bean> <!-- This is spring jms Connection pool provided to us --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <property name="targetConnectionFactory" ref="producerTargetConnectionFactory"/> </bean> <!-- To make a message in queue mode, you need a message destination --> <!-- A queue destination, which is point-to-point, i.e. P2P. So the queue mode is also called P2P Pattern --> <!-- Here you pass in a construction parameter that is the name of the start of the queue destination--> <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="queue"/> </bean> <!-- To configure JmsTemplate,For sending messages--> <!-- Note: The parameters here are spring Connection pool provided to us--> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="connectionFactory"/> </bean> <bean class="com.happybks.jms.producer.ProducerServiceImpl"/> </beans>
Finally, we write a caller class for producer services, call our configured producer services, and send 100 messages to the message middleware:
package com.happybks.jms.producer; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppProducer { public static void main(String[] args) { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("producer.xml"); ProducerService service = context.getBean(ProducerService.class); for (int i = 0; i < 100; i++) { service.sendMessage(i+". hello! happyBKs!"); } //Let the spring container clean up all resources, including connections, or connections will persist. context.close(); } }
Run program, send message
View management interface http://127.0.0.1:8161
Note that this is the port of the activemq administration page, 8161.The service port just configured in the code is the tcp protocol, port 61616.
spring JMS queue mode consumer
We also need to create a program for consumers to build a separate spring container configuration file consumer.xml.But we found that many bean s, such as link factories, destination destinations, and so on, are generic, so we pulled out the parts that are common to both producers and consumers, made a common.xml separately, and referenced the resources in consumer.xml and producer.xml.
common.xml
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!-- This is ActiveMQ For us ConnectionFactory --> <bean id="producerTargetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://127.0.0.1:61616"/> </bean> <!-- This is spring jms Connection pool provided to us --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <property name="targetConnectionFactory" ref="producerTargetConnectionFactory"/> </bean> <!-- To make a message in queue mode, you need a message destination --> <!-- A queue destination, which is point-to-point, i.e. P2P. So the queue mode is also called P2P Pattern --> <!-- Here you pass in a construction parameter that is the name of the start of the queue destination--> <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="queue"/> </bean> </beans>
producer.xml
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <import resource="common.xml"/> <!-- To configure JmsTemplate,For sending messages--> <!-- Note: The parameters here are spring Connection pool provided to us--> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="connectionFactory"/> </bean> <bean class="com.happybks.jms.producer.ProducerServiceImpl"/> </beans>
consumer.xml
Here we add consumer.xml as the spring container for consumer client programs, where we need to create two additional bean s, one for message listeners and one for message containers (to manage connections to connection factories and to listen for messages through message listeners)
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--Import Public Configuration--> <import resource="common.xml"/> <!--Configure message listeners--> <bean id="consumerMessageListener" class="com.happybks.jms.consumer.ConsumerMessageListener"/> <!--Configure Message Container--> <!--Create a JMS A container for message listening, which is entirely composed of spring For us.--> <!--The purpose of this container is to manage the connections in the container so that it automatically connects to our connection factory; to designate our message destinations and our message listeners.--> <!--So you need to configure a connection factory, a message listener--> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <!--Indicates the address of the message to listen on--> <property name="destination" ref="queueDestination"/> <!--Configure message listeners--> <property name="messageListener" ref="consumerMessageListener"/> </bean> </beans>
Message listeners require that we inherit the MessageListener interface and implement it ourselves.
It overrides the onMessage method for processing messages after they are received.
package com.happybks.jms.consumer; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class ConsumerMessageListener implements MessageListener { //The onMessage method called means that the MessageListener has received the message. @Override public void onMessage(Message message) { TextMessage textMessage=(TextMessage)message; try { System.out.println("Consumers receive messages:"+textMessage.getText()); } catch (JMSException e) { e.printStackTrace(); } } }
Similarly, we write a client startup class:
package com.happybks.jms.consumer; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AppConsumer { public static void main(String[] args) { final ApplicationContext applicationContext = new ClassPathXmlApplicationContext("comsumer.xml"); // Since message reception is an asynchronous process, if you close the application here, it will be closed along with the connection, etc., which may result in incomplete message reception, so we will not close() } }
Run the program:
View management interface http://127.0.0.1:8161
Multi-client receive experiment in queue mode:
We started two consumers as we did in the previous article, waiting to receive messages from the JMS messaging middleware.
We then start a producer and send 100 messages:
Found two consumers sharing the message equally.
Subject Mode JMS Publishers and Subscribers
The code implementation for publishers and subscribers in thematic mode is essentially the same as in previous queue mode, so here are just three things that need to be changed:
(1) A new desitnation bean is configured in the spring container configuration file common.xml, which is of type Destination's subject subclass:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <!-- This is ActiveMQ For us ConnectionFactory --> <bean id="producerTargetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://127.0.0.1:61616"/> </bean> <!-- This is spring jms Connection pool provided to us --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <property name="targetConnectionFactory" ref="producerTargetConnectionFactory"/> </bean> <!-- To make a message in queue mode, you need a message destination --> <!-- A queue destination, which is point-to-point, i.e. P2P. So the queue mode is also called P2P Pattern --> <!-- Here you pass in a construction parameter that is the name of the start of the queue destination--> <bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="queue"/> </bean> <!--One Theme Destination, Publish Subscription Mode--> <bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="topic"/> </bean> </beans>
(2) Change the annotation of the injection reference to destation in the publisher (producer) service implementation class by replacing the name of @Resource with the new Destination theme destination bean we just configured, topicDestination.
package com.happybks.jms.producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.MessageCreator ; import javax.annotation.Resource; import javax.jms.*; public class ProducerServiceImpl implements ProducerService { @Autowired JmsTemplate jmsTemplate; //The reason @Resouce is used is that there may be multiple Destination objects in the project, where we need to inject by the id of the bean instead of @Autowired by type //@Resource(name = "queueDestination") @Resource(name = "topicDestination") Destination destination; @Override public void sendMessage(final String message) { final MessageCreator messageCreator = new MessageCreator() { //Implementing the createMessage method of the MessageCreator interface requires the creation of messages.Creating a message requires a Session session. //Create a message @Override public Message createMessage(Session session) throws JMSException { TextMessage textMessage = session.createTextMessage(message); return textMessage; } }; //Send messages using jmsTemplate jmsTemplate.send(destination, messageCreator); System.out.println("Send a message:" + message); } }
(3) Modify the property destination of the message container jmsContainer in the subscriber (consumer) spring container configuration file comsumer.xml to topicDestination
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--Import Public Configuration--> <import resource="common.xml"/> <!--Configure message listeners--> <bean id="consumerMessageListener" class="com.happybks.jms.consumer.ConsumerMessageListener"/> <!--Configure Message Container--> <!--Create a JMS A container for message listening, which is entirely composed of spring For us.--> <!--The purpose of this container is to manage the connections in the container so that it automatically connects to our connection factory; to designate our message destinations and our message listeners.--> <!--So you need to configure a connection factory, a message listener--> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <!--Indicates the address of the message to listen on--> <!--<property name="destination" ref="queueDestination"/>--> <property name="destination" ref="topicDestination"/> <!--Configure message listeners--> <property name="messageListener" ref="consumerMessageListener"/> </bean> </beans>
With these three modifications, we have successfully changed the code from the queue mode (or P2P mode) to the theme mode scenario.
Run the theme mode program:
First, we start two subscriber clients, noting that the subscriber must be started before the publisher, because in thematic mode, subscribers can only receive messages published by the publisher on the JMS middleware for the time after their subscription, which they cannot receive before their subscription.
Then we launch the publisher program and send 100 messages:
Instantly, both subscriber programs received messages that were complete and identical:
Attachment: