JMS is a standard Java API that allows a Java application to send messages to another application. It is highly scalable and allows us to loosely couple applications using asynchronous messaging. Using JMS we can read, send and read messages.
Here are some implementations of JMS is as follows:
- Amazon SQS
- Apache ActiveMQ
- JBoss Messaging
- RabbitMQ
JMS Message
A JMS message can be divided into three parts that are as follows:
- Header: It contains the metadata about the message.
- Properties: It can further be subdivided into three sections –
- Application: The java application sending message.
- Provider: It is used by the JMS provider and is implementation-specific.
- Standard Properties: These are defined by the JMS API.
- Payload: This field is the message itself.
Implementation: Here we will be building a sample greeting (Maven-based)application to demonstrate how to integrate and use JMS. For simplicity, we will be using an embedded server instead of creating another application.
The project Structure is as follows:
Step 1: Create a spring project using spring initializer. Create the folders and files as depicted in the below image as follows:
Step 2: Add the below dependencies to pom.xml file.
XML
< dependency > < groupId >org.apache.activemq</ groupId > < artifactId >artemis-server</ artifactId > </ dependency > < dependency > < groupId >org.apache.activemq</ groupId > < artifactId >artemis-jms-server</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-artemis</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-devtools</ artifactId > < scope >runtime</ scope > < optional >true</ optional > </ dependency > < dependency > < groupId >org.projectlombok</ groupId > < artifactId >lombok</ artifactId > < optional >true</ optional > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > </ dependency > |
Model Layer
Step 3: Create a simple POJO (Plain old java class) which will act as our model for sending and receiving messages. Here we have used Lombok to reduce boilerplate code. Annotations used are as follows:
- @Data: This annotation generates getters, setters for all the fields, toString method, and equals & hashCode method.
- @Builder: This annotation uses a builder pattern to automatically build complex builder APIs.
- @NoArgsConstructor: This annotation generates a constructor with no arguments.
- @AllArgsConstructor: This annotation generates a constructor with all arguments.
Example:
Java
// Java Program to Illustrate Model Layer package com.anuanu.springjms.model; // Importing required classes import java.io.Serializable; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; // Annotation @Data @Builder @NoArgsConstructor @AllArgsConstructor // Class // Implementing Serializable interface public class GreetingMessage implements Serializable { // Class data members static final long serialVersionUID = -7462433555964441775L; private UUID id; private String message; } |
Required Configurations is as follows:
A. Embedded Server Configuration
As discussed earlier, we will be creating an embedded server for the demonstration of JMS messaging. We will be using ActiveMQServers to create our embedded server. Here is the method signature to newActiveMQServer:
Modifier and Type | Methods and Description |
---|---|
static ActiveMQServer | newActiveMQServer(Configuration config) |
Note: Configuration can be changed as per the requirement.
The embedded server also starts when our spring boot application starts. (as we have called start() method on the server).
Example:
Java
// Java Program to Illustrate Embedded Server Configuration package com.anuanu.springjms; // Importing required classes import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServers; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringJmsApplication { // Main driver method public static void main(String[] args) throws Exception { // Embedded Server Configuration ActiveMQServer activeMQServer = ActiveMQServers.newActiveMQServer( new ConfigurationImpl() .setPersistenceEnabled( false ) .setJournalDirectory( "target/data/journal" ) .setSecurityEnabled( false ) .addAcceptorConfiguration( "invm" , activeMQServer.start(); SpringApplication.run(SpringJmsApplication. class , args); } } |
C. Task Configuration
The taskConfig class will help us execute tasks asynchronously. The task here is sending messages at fixed intervals of time. For sending messages at fixed intervals we have enabled the scheduler using @EnableScheduling annotation. Annotations used are as follows:
- @EnableScheduling: This annotation enables the scheduler for our application.
- @EnableAsync: This annotation enables spring to run @Async methods in a background thread pool.
- @Configuration: This annotation indicates that the specified class has @Bean definition methods.
- @Bean: It is a method-level annotation and is used to explicitly declare a bean.
Example:
Java
// Java Program to Illustrate Task Configuration package com.anuanu.springjms.config; // Importing required classes import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; // Annotations @EnableScheduling @EnableAsync @Configuration // Class public class TaskConfig { // Task Configuration @Bean TaskExecutor taskExecutor() { return new SimpleAsyncTaskExecutor(); } // taskExecutor bean is injected into spring context, // and spring will use it to execute tasks for us } |
D. Message Converter Configuration
The jmsConfig class provides a common stream for producers and consumers for producing and consuming messages respectively. It also provides a bean for converting Java objects and JMS messages. Annotations used are as follows:
- @Configuration: This annotation indicates that the specified class has @Bean definition methods.
- @Bean: It is a method-level annotation and is used to explicitly declare a bean.
Note : Message converter uses Jackson 2.x to convert messages to and from JSON. It maps an object to a BytesMessage, or to a TextMessage if the targetType is set to MessageType.TEXT. It converts from a TextMessage or BytesMessage to an object.
Example:
Java
// Java Program to Illustrate Task Configuration package com.anuanu.springjms.config; // Importing required classes import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jms.support.converter.MappingJackson2MessageConverter; import org.springframework.jms.support.converter.MessageConverter; import org.springframework.jms.support.converter.MessageType; // Annotations @Configuration // Class public class JmsConfig { // Class data member public static final String QUEUE = "greet" ; // Annotation @Bean // Class public MessageConverter messageConverter() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName( "_type" ); return converter; } // Enabling spring to take jms messages and flip it // to a json message. then it can read // that jms message as a jms text message and // convert it back to java object } |
Sending JMS Messages
The MessageSender class (described below) is mainly used to create and produce/send messages to the common stream from where the consumer can consume messages. We have used JmsTemplate which is a helper class that makes it easier for us to receive and send messages through JMS. We have also annotated the class with @Scheduled with a value of 2000 ms which tells the scheduler to send the messages at an interval of every 2 seconds. Annotations used are as follows:
- @RequiredArgsConstructor : This annotation generates a constructor with required arguments i,e. arguments with final fields or fields with other constraints.
- @Component: This annotation marks our class as the component which allows spring to detect any custom-defined beans.
- @Scheduled: This annotation marks a method to be scheduled. It must have any one of these cron(), fixedDelay() or fixedRate() attributes.
Example:
Java
// Java Program to Illustrate Sending JMS Messages package com.anuanu.springjms.sender; // Importing required classes import com.anuanu.springjms.config.JmsConfig; import com.anuanu.springjms.model.GreetingMessage; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.jms.core.JmsTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; // Annotations @RequiredArgsConstructor @Component // Class public class MessageSender { // Class data member private final JmsTemplate jmsTemplate; private static Integer ID = 1 ; // Annotation @Scheduled (fixedRate = 2000 ) // Method public void sendMessage() { // Display command System.out.println( "Greetings user" ); GreetingMessage message = GreetingMessage.builder() .id(UUID.randomUUID()) .message( "Greetings user " + ID++) .build(); jmsTemplate.convertAndSend(JmsConfig.QUEUE, message); // Display command System.out.println( "Message sent!!!" ); } } |
Receiving JMS Messages
The MessageListener class (described below) acts as a consumer i.e. it consumes/receives messages that reside in the common stream and is not already consumed. The location of the common stream “JmsConfig.QUEUE” is passed to the destination method in @JmsListener. Annotations used are as follows:
- @JmsListener: This annotation marks a method to be the target of a JMS message listener on the specified destination().
- @Component: This annotation marks our class as component which allows spring to detect any custom-defined beans.
- @Payload: This annotation marks that the payload of the message is to be extracted is annotated parameter.
- @Headers: This annotation extracts all the headers inside a Map<String, Object>. It is used here with MessageHeaders class which implements Map<String, Object> and is used for message headers.
Example:
Java
// Java Program to Illustrate Receiving JMS Messages package com.anuanu.springjms.listener; // Importing required classes import com.anuanu.springjms.config.JmsConfig; import com.anuanu.springjms.model.GreetingMessage; import javax.jms.Message; import org.springframework.jms.annotation.JmsListener; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.handler.annotation.Headers; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.stereotype.Component; // Annotation @Component // Class public class MessageListener { @JmsListener (destination = JmsConfig.QUEUE) public void listen( @Payload GreetingMessage greetingMessage, @Headers MessageHeaders messageHeaders, Message message) { // Display command System.out.println( "Greeting Received!!!" ); System.out.println(greetingMessage); } } |
Output:
The message is sent and received every 2 seconds. A unique user name and id is passed for every new message.
Inspecting the messageListener class:
The below image shows us the jms message that is received at the messageListener class. There is a variety of properties that can be customized in the header field. Notice the one key-value pair we added in the header.
'_type' -> 'com.anuanu.springjms.model.GreetingMessage'