Spring Boot and Weblogic JMS – Part II
This article is the continuation of the two part series on Weblogic 14c JMS and using Spring Boot to send and receive message(Part-I). But even then if you are familiar with setting up your weblogic server with JMS resources, you can follow along to see how spring can be used with it.
In This part of the article we will develop a spring boot application which will connect to a JMS Queue running inside Weblogic and we will send a custom message using JmsTemplate class. We will also create Message Driven POJO(MDP, equivalent of MDB from Java EE world except for the fact that it is just a POJO) and we will make it listen to the messages on the configured Queue.
Let’s Begin!
What will we build?
We are going to build a REST application which interacts with Weblogic 14c JMS using our favorite Spring Boot.
Creating Spring Boot REST API:
First we will create a simple Spring Boot application which will be packaged as WAR file and then we will add the two needed xml files, weblogic.xml and dispatcherServlet-servlet.xml, into WEB-INF folder. If you are used to building hard-core Java EE apps then I would suggest to look at this article which will explain you in detail how you can make your spring boot app work with Weblogic 14c.
Once our app is ready first thing which we will do is add the required dependencies for JMS in our pom.xml.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency>
As we have ActiveMq dependencies, we will have instruct spring container to not initialize spring with default ActiveMq settings and create some default message broker. The way to do that is to tell SpringBootApplication annotation to exclude ActiveMQAutoConfiguration class.
@SpringBootApplication(exclude = ActiveMQAutoConfiguration.class) public class WeblogicJmsApplication { public static void main(String[] args) { SpringApplication.run(WeblogicJmsApplication.class, args); } }
Now let’s create a custom model object named as CustomMessage and we will use this class to send the message. For the sake of simplicity we will keep only two attributes there an Id and string property to hold the message. The idea here is to showcase how can send custom Java objects as messages(without doing explicit serialization or deserialization).
package narif.poc.wlseries.weblogicjms.model; import lombok.AllArgsConstructor; import lombok.Data; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @Data @AllArgsConstructor public class CustomMsg { private Long id; @NotNull @NotEmpty private String message; @Override public String toString() { return "CustomMsg{id=" + id +", message='" + message + "'}"; } }
Oh yes I am using project Lombok and toString could also be removed.
I have created a Pojo which I intend to send as a JMS message to a Queue but CustomMsg class will not serialized on its own. So to make custom POJOs serializable spring provides MessageConverters. Either we can use the ones which are provided by spring or if we want we can also create our own custom converters(yes, spring strongly follows Convention over Configuration, also known as known as coding by convention). One such converter is MappingJackson2MessageConverter. Yes you guessed it! It serializes your POJO to JSON using Jackson.
So let us define the MessageConverter bean in a config class named as JmsConfig.
package narif.poc.wlseries.weblogicjms.config; 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; @Configuration public class JmsConfig { @Bean public MessageConverter jacksonJmsMessageConverter(){ MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName("_type"); return converter; } }
Till now we have defined our CustomMsg class and the bean definition which specifies the MessageConverter to use while Serializing our POJO. Once spring finds a MessageConverter bean defined as part of our configuration, it will use it for both serialization and deserialization of our POJO.
Another small configuration which is still pending is to specify the Connection Factory details for the spring container and we do that in our application.yml file:
spring: jms: jndi-name: jms/myConnectionFactory
Now we are good to send messages to our pre-configured Queue in weblogic. Spring will automatically create a JmsTemplate instance will inject to us to send messages. But as it will be a default JmsTemplate we will have to explicitly specify the jndi name of our queue while sending a message.
We can choose to override the default JmsTemplate bean and we can specify a default destination for it. So that while sending a message if a destination is provided, then send the message to it otherwise use the default one configured with the JmsTemplate. We will put this configuration in our application.yml file:
spring: jms: jndi-name: jms/myConnectionFactory template: default-destination: jms/myTestQueue
We have all the pieces in place to send our first message. So let’s create a REST service to do the same:
package narif.poc.wlseries.weblogicjms.restservice; import narif.poc.wlseries.weblogicjms.model.CustomMsg; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.core.JmsTemplate; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @RestController @RequestMapping(path = "queue") public class QueueRestService { @Autowired private JmsTemplate jmsTemplate; @PostMapping public String postMessage(@RequestBody @Valid CustomMsg customMsg){ prepareFinalMsgForDefaultQ(customMsg); jmsTemplate.convertAndSend(customMsg); return "Message Sent!"; } private void prepareFinalMsgForDefaultQ(@RequestBody @Valid CustomMsg customMsg) { @NotNull @NotEmpty String msg = customMsg.getMessage(); String finalMsg = msg + ". MSG FOR DEFAULT QUEUE."; customMsg.setMessage(finalMsg); } }
NOTE: Before we run our mvn package command we will have make sure that our spring does not tries to initialize test cases without having an initial context set. So a very bad solution to it is to disable spring boot tests temporarily. There are ways in which you can define bean to enable jndi naming in local context but that is part of another story.
After running mvn package lets deploy the war to wls and post a message using postman:
We can now see the sent message in weblogic admin console.
For that we will have to go to our pre-configured queue in wls and in there we will switch to Monitoring Tab.
We see here that there is one message inside the queue. We can select the check box and click on show messages. This will display all the messages inside this queue.
Now if we click on any individual message, we can actually check the contents of the message.
Here we bunch of JMS related information along with the actual Text message in JSON format. This JSON was created by the Message Converter bean which we had defined earlier.
Our message sender is complete. Now we will create Message Driven POJO to asynchronously listen to messages from Queue.
Creating MDP
To listen to messages, first we will have to tell spring that our app is going have a MDP. To do so we will annotate our main class with EnableJms annotation:
package narif.poc.wlseries.weblogicjms; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; import org.springframework.jms.annotation.EnableJms; @SpringBootApplication(exclude = ActiveMQAutoConfiguration.class) @EnableJms public class WeblogicJmsApplication { public static void main(String[] args) { SpringApplication.run(WeblogicJmsApplication.class, args); } }
After this we will specify a JmsListenerContainerFactory bean in our JmsConfig class. JmsListenerContainerFactory is an interface and the implementation class which we will be using is the DefaultJmsListenerContainerFactory. We can specify several properties for our listeners using this spring provided implementation. If we wish to further customize how the class behaves, for instance let us say that we want to change the ExecutorService of our own choosing, then we can do so by extending this DefaultJmsListenerContainerFactory and overriding the behaviour we see fit for our need.
For the sake of simplicity we will use DefaultJmsListenerContainerFactory as an implementation for JmsListenerContainerFactory and not a custom one. So lets add the bean definition in our JmsConfig class:
@Bean public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer){ DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setSessionTransacted(true); factory.setConcurrency("3-10"); configurer.configure(factory, connectionFactory); return factory; }
Now it is time to create our first MDP:
package narif.poc.wlseries.weblogicjms.listeners; import narif.poc.wlseries.weblogicjms.model.CustomMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jms.annotation.JmsListener; import org.springframework.stereotype.Component; import javax.validation.Valid; import java.time.LocalDateTime; @Component @Transactional public class JmsQueueListener { private static final Logger LOGGER = LoggerFactory.getLogger(JmsQueueListener.class.getName()); @JmsListener(destination = "jms/myTestQueue") public void listenToMessages(@Valid CustomMsg customMsg){ String text = "MESSAGE RECEIVED AT: "+ LocalDateTime.now()+". MSG: "+customMsg; LOGGER.info(text); } }
Our MDP is a simple POJO. We annotate it with Component annotation so that spring can pick it up and will using Spring’s declarative transaction management for all our methods inside the POJO. The important aspect here is the JmsListener annotation where we specify the destination from where we want to receive the messages. For keeping things as simple as possible we will simply log the message to our console.
And great we see both our messages are received by our listener.
Apart from using the default listener, we can also specify different Queue and then use JmsTemplate to send message to it and a listener method to listen to those messages.
In the application yml I will define a property to hold the jndi name of a different distributed queue(yes, we will a different kind of Queue all together, though we could have used non distributed Queue as well).
jms: queue: jndi-name: jms/myTestDq
After adding the jndi name in our application yml file, we will expose a post service to send messages to the newly created distributed queue.
@Value("${jms.queue.jndi-name}") private String distributedQueueJndiName; @PostMapping("dq") public String postMessageToDq(@RequestBody @Valid CustomMsg customMsg){ prepareFinalMsg(customMsg, "<MSG FOR DISTRIBUTED QUEUE>"); jmsTemplate.convertAndSend(distributedQueueJndiName, customMsg); return "Message Sent to Distributed Queue!"; }
And in our listener POJO we will add another listener method
@JmsListener(destination = "${jms.queue.jndi-name}") public void receiveDistributedQueueMsg(@Valid CustomMsg customMsg){ logTheCustomMsg(customMsg); }
Now time to see if things work when deployed on WLS:
And the listener in action:
With this I conclude the article on Spring Boot and Weblogic 14c JMS. The whole project is present at my github repository.
I would suggest to go through the spring JMS documentation to develop deeper understanding of how things work? Spring JMS Documentation
Link to GitHub: The Complete App
Lead Software Engineer at Syniverse || Ex-Oracle || Spring Boot || Spring Cloud || JPA || Micro-services || Docker || AWS || EKS || JMS || JAX-WS || RestAPI
2yHi Sir Code deployed on 11g, it's showing below error. Error java.lang.StringIndexOutOfBoundsException: String index out of range: 4800
Senior Software Engineer at Cognizant
3yHi thank you. How can I use xml data instead of JSON. Is there any message converter like MappingJackson2MessageConverter for xml data type. Can you please help me in this.
Technical Lead at Banco de Chile
4yHi, Good Article, but i have a problem: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial I am using my own weblogic and apply the change on the properties file. Could you help me?