Thursday, 19 April 2012

JMS with Spring configured in Java

Handling JMS using standard JMS API is a tedious task - you need to create a lot of boilerplate code.
Spring provides an abstraction layer that eases this pain.
In the following example I'm using ActiveMQ (which needs to be started before running any of the code below) but Spring works with other major JMS implementations as well.

In order to send a JMS message we can use JMS template. Here's our sender that's using it:

public class JmsSender {
private JmsTemplate jmsTemplate;
public void sendText(final String text) {
jmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(text);
}
});
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
}
view raw JmsSender.java hosted with ❤ by GitHub

Spring beans needs to be configured in a context. Let's define Spring configuration in Java code:

@Configuration
public class JmsSenderSpringConfig {
@Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setDefaultDestination(new ActiveMQQueue("jms.queue"));
jmsTemplate.setConnectionFactory(connectionFactory());
return jmsTemplate;
}
@Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL("tcp://localhost:61616");
return activeMQConnectionFactory;
}
@Bean
public JmsSender jmsSender() {
JmsSender jmsSender = new JmsSender();
jmsSender.setJmsTemplate(jmsTemplate());
return jmsSender;
}
}

JMS template must have a connection factory with a broker url and a destination (in our case it's a queue).
The XML configuration equivalent would look like:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="defaultDestinationName" value="jms.queue"/>
</bean>
<bean id="jmsSender" class="pl.mjedynak.jms.sender.JmsSender">
<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
</beans>

Java configuration provides not only compile-time checking but in my opinion it is more straightforward and easier to control.
Unfortunately we can't escape XML completely, we need to turn on component scanning so that Spring will read configuration from classes in our package:

<?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:component-scan base-package="pl.mjedynak"/>
</beans>

The example code that would run the sender:

public class JmsSenderRunner {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("sender-context.xml");
JmsSender jmsSender = (JmsSender) applicationContext.getBean("jmsSender");
jmsSender.sendText("hellooooo " + new Date());
}
}
Apart from the sender we can also create the receiver. One of the solutions is to make him asynchronous by implementing MessageListener interface.

public class JmsReceiver implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println("Received: " + message);
}
}

The Spring config looks as follows:

@Configuration
public class JmsReceiverSpringConfig {
@Bean
public JmsReceiver jmsReceiver() {
return new JmsReceiver();
}
@Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL("tcp://localhost:61616");
return activeMQConnectionFactory;
}
@Bean
public DefaultMessageListenerContainer messageListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(connectionFactory());
messageListenerContainer.setDestinationName("jms.queue");
messageListenerContainer.setMessageListener(jmsReceiver());
return messageListenerContainer;
}
}

Our receiver is set in the message listener container with the connection factory and the destination.
Receiver is missing the same XML configuration as sender, and the running code is analogical.

Here's the list of maven dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>3.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.5</version>
</dependency>
view raw deps.xml hosted with ❤ by GitHub

No comments:

Post a Comment