Chapter 5. Spring JMS

The Java Messaging Service (JMS) API, which came into existence a decade ago, was an instant hit in the area of distributed messaging and enterprise applications. The demand for easier and standardized integration with the Messaging Middleware was the drive for creating this technology. Although the API is straightforward and relatively easy, Spring has taken a step forward in creating an even easier framework around it. This chapter will introduce you to the JMS basics and explains Spring’s take on JMS and how it simplified developers’ lives even further.

Two-Minute JMS

JMS provides a mechanism of communication between applications without being coupled. The applications can talk to each other via the JMS provider without even knowing who is on the other side of the fence.

For example, a PriceQuote service can publish price quotes onto a channel, expecting someone to receive them. However, the service may not be aware of any consumers that would consume these quotes. Similarly, the consumers may not have any clue about the source of the data or the producers. In a way, they are hidden behind the walls, just interacting via destinations or channels. Producers and consumers don’t have to bother about each other’s existence as long as they know their expected messages are delivered or received, respectively.

There are three important moving pieces in JMS: the service provider, the producer, and the consumer.

The service provider is the central piece, while the other two are either data producers or consumers. The architecture is based on hub and spoke; that is, the server maintains a central ground like a hub while producers and consumers act like the spokes.

The overall responsibility in this architecture is taken by the service provider. The provider makes sure all the consumers receive the messages, while at the same time, all producers are able to publish the messages. I would recommend picking up a book on the subject to understand JMS in detail.

Messaging Models

In JMS, there are primarily two messaging models: one is point-to-point (or P2P, as it is sometimes called) and the other is publish-subscribe (Pub/Sub). There are specific use cases where you should use these models. Although both differ fundamentally in delivery and reception mechanisms, a unified API is used to access both the models.

Point-to-point messaging

In point-to-point (P2P) mode, a message is delivered to a single consumer via a destination—it’s for a specific consumer only. A publisher publishes a message onto a destination, while a consumer consumes the message off that destination. This is analogous to a birthday greeting card sent to you—only to you!

When a message is published in P2P mode to a queue, one and only one consumer can receive that message. Even if there are hundreds of consumers connected to that queue, still only one consumer will get that message delivered. Of course, you never know who’s the lucky winner though!

The destination in a point-to-point model is called a Queue.

Pub/sub messaging

On the other hand, if a message is published to a destination, there could be several subscribers each receiving a copy of the message. The publisher obviously publishes the message once. All the subscribers interested will head to the same destination to consume that message. The JMS Provider makes sure each of the subscribers receives a copy of the message. This is analogous to a car dealer sending offers to you and possibly thousands of others!

The destination in a Pub/Sub model is called a Topic.

Spring JMS

The Spring framework has simplified the JMS API quite a bit. All we need to do is follow a simple template software pattern in order to understand the Spring JMS usage. Before we go into code examples, let me explain the high-level classes and their usage.

The core of Spring JMS revolves around a single class, JmsTemplate. If you understand this one class in the Spring JMS framework, you are good to go! The JmsTemplate class is heavily used in Spring JMS for both message consumption and production. The only case JmsTemplate is not used is for asynchronous message consumption. We will discuss this case a bit later in the chapter.

First, let’s start digging into the JmsTemplate class.

Mother of All: The JmsTemplate Class

We can use the JmsTemplate class for both publishing and consuming the messages. The template class hides all the plumbing needed for connecting to a provider. It also abstracts away lots of boilerplate code for publishing or receiving the messages. It manges the resources, such as connections, behind the scenes.

When using Spring JMS, the usual way is to configure a JmsTemplate instance (similar to a normal bean) and inject into our bean. We can set some Qualitiy of Service (QOS) properties such as priority, and delivery mode on the class, but the ConnectionFactory property is mandatory. The ConnectionProperty provides the enough information about the service provider to connect to it. There are other properties that are required for more features (such as defaultDestination and receiveTimeout parameters), which we will see in the following sections.

The template works by using callsbacks—callbacks such as MessageCreator for the message creation, SessionCallback for associating with a Session, and ProducerCallback for creating a message producer.

Once configured and instantiated, the JmsTemplate instance is thread-safe, so it can be shared across different beans without having to worry about session corruption.

There are primarily two ways of creating or instantiating JmsTemplate. One is to create the class by using a normal new operator, while the other is to declare and configure it in a configuration file. Both of them require us to pass in a ConnectionFactory property, though.

The TradePublisher class, shown here, uses a JmsTemplate for its message publication. The JmsTemplate is instantiated by passing in the constuctor argument - a connectionFactory instance.

public class TradePublisher {
  private String destinationName = "justspring.core.jms.trades";
  private JmsTemplate jmsTemplate = null;

  public void setConnectionFactory(ConnectionFactory connectionFactory) {
    // create the JmsTemplate using the injected ConnectionFactory 
    jmsTemplate = new JmsTemplate(connectionFactory);
  }
  ...
}

Once we have the TradePublisher created with a JmsTempate instance, the next step is to use this instance to publish our messages. There is a send method on the jmsTemplate instance that we should invoke to publish a message. There are basically two variants of the send method—one that takes in a destination and another that doesn’t—the MessageCreator callback is mandatory though. We’ll go through them in a few minutes.

The following snippet shows the usage:

public class TradePublisher {
  // access the template when publishing the message
  public void publishTrade(final Trade t) {
    jmsTemplate.send(destinationName, new MessageCreator() {
    @Override
    public Message createMessage(Session session) throws JMSException {
      ObjectMessage msg = session.createObjectMessage();
      msg.setObject(t);
        return msg;
      }
    });
  }
}

The only requirement in this case is to wire the ConnectionFactory to the publisher in your config so it can be injected when the bean is created. Of course, you need to instantiate the JmsTemplate object, using this connection factory. Here’s the XML file:

<bean name="tradePublisher" 
  class="com.madhusudhan.jscore.jms.TradePublisher">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

<bean id="connectionFactory" 
  class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616" />
</bean>

As we discussed earlier, there is another way to get a hold of JmsTemplate in our bean. We can wire the template with a connection factory and inject into our bean—this is all done in the config. If you carefully observe, all we did was moving the creation of template from our application to the framework.

<bean name="tradePublisher" class="com.madhusudhan.jscore.jms.TradePublisher">
  <property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
</bean>
<bean id="connectionFactory" class=
  "org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616" />
</bean>

Your bean will have a reference to JmsTemplate via a setter method. The TradePublisher with a wired-in template as shown in the following snippet:

public class TradePublisher {
  private String destinationName = "justspring.core.jms.trades";
  private JmsTemplate jmsTemplate = null;
  
  public void setJmsTemplate(JmsTemplate jmsTemplate) {
    this.jmsTemplate = jmsTemplate;
  }

  public JmsTemplate getJmsTemplate() {
    return jmsTemplate;
  }
  
  public void publishTrade(final Trade t) {
    ...
  }
}

The JmsTemplate is injected into the related class that requires publishing or receiving messages from a JMS server, in this case the TradePublisher. As you can see, the ConnectionFactory is already wired to the JmsTemplate declaratively.

As we mentioned earlier, for sending messages, the JmsTemplate exposes these three methods:

send(MessageCreator msgCreator) //Default Destination

send(String destinationName, MessageCreator msgCreator)

send(Destination destinationName, MessageCreator msgCreator)

The first one assumes that messages should end up in a default destination, while the next two methods specify the destination. The MessageCreator argument is a callback hook so we can create the appropriate JMS Message. The callback has one method, createMessage(Session session). Using this session object, we create one of the types of Message with data.

Check out the publishTrade method implementation above in the TradePublisher class now to get a complete picture.

In the JMS world, all messages should be declared as one of the predefined five types before attempting to publish or receive them. The five types are TextMessage, BytesMessage, ObjectMessage, StreamMessage, and MapMessage. You should consult the JMS API for the details of these messages.

Publishing Messages

Let’s develop an example to understand the mechanics of message publication.

The StudentEnroller is the publishing component in the workflow that would be invoked whenever a Student enrolls for a course. The aim is to publish the enrolled Student onto a JMS destination so the parties interested will consume and act on them (probably to lure the student to take one of their subjects or to provide info about the library facilities, cafeteria, or unions).

The following snippet shows the code:

public class StudentEnroller {
  private String destinationName = null;
  private JmsTemplate jmsTemplate = null;

  public void publish(final Student s) {
    getJmsTemplate().send(getDestinationName(), new MessageCreator() {
      @Override
      public Message createMessage(Session session) throws JMSException {
        TextMessage m = session.createTextMessage();
        m.setText("Enrolled Student: " + s.toString());
        return m;
        }
      });
    }
  // setters and getters for the 
  //jmsTemplate and destinationName variables
  ...
}

The StudentEnroller has two instance variables: jmsTemplate and destinationName. The JmsTemplate is used to publish the messages and the destinationName defines the location (queue or topic) where the messages should be sent.

The StudentEnroller instance is injected with a JmsTemplate instance and a destinationName value at runtime. The publish method on the StudentEnroller instance is invoked when a client wishes to publish a Student message.

In order to create a JMS Message, we should use Spring’s MessageCreator implementation. The template class expects a new instance of this callback with createMessage() being implemented:

public Message createMessage(Session session) throws JMSException {
  TextMessage m = session.createTextMessage();
  m.setText("Enrolled Student: " + s.toString());
  return m;
}

The appropriate message is created using the session, in this case a TextMessage.

Before injecting the JmsTemplate into StudentEnroller, it is fully configured in the Spring’s XML file. The template also has a variable called connectionFactory that needs to be defined and referenced. The connection factory is specific to individual JMS Providers. I am going to use ActiveMQ as the JMS Provider for the rest of the chapter, but you can use any other providers that are JMS-compliant (so the code should work without tweaking for each provider).

The ActiveMQ connection factory requires a brokerUrl property to be set as shown here:

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
</bean>
<bean id="connectionFactory" class=
  "org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

The last piece is the declaration of the StudentEnroller bean itself in the config file wired with the two properties jmsTemplate and destinationName.

<bean name="studentEnroller" class=
  "com.madhusudhan.jscore.jms.pub.StudentEnroller">
  <property name="destinationName" value="topic.STUDENTS"/>
  <property name="jmsTemplate" ref="jmsTemplate"/>
</bean>

Write a simple test client that creates a Spring container by loading the jms-pub-beans.xml config file.

public class StudentEnrollerClient {
  private ApplicationContext ctx = null;
  private StudentEnroller enroller = null;
  public StudentEnrollerClient() {
    ctx = new ClassPathXmlApplicationContext("jms-pub-beans.xml");
    enroller = (StudentEnroller) ctx.getBean("studentEnroller");
  }
  public void publishStudent(Student s) {
    System.out.println("Publishing Student..");
    enroller.publish(s);
  }
  public static void main(String[] args) {
    StudentEnrollerClient client = new StudentEnrollerClient();
    Student s = new Student();
    client.publishStudent(s);
  }
}

The client gets ahold of the StudentEnroller instance to invoke the publish method with a new Student object. The StudentEnroller was already injected with the dependencies such as jmsTemplate and destinationName (see the config declaration).

Now, run the ActiveMQ server. The server is started and running on our local machine (hence, localhost) on a default port 61616.

Run the client and if all is set correctly, we should see a message landing up in the ActiveMQ destination.

Publishing Student..
Successfully published student message to topic.STUDENTS

Note that the ActiveMQ creates the required destinations (in this case the topic.STUDENTS topic) on demand if they do not exist.

Sending Messages to Default Destination

If you wish to send the messages to a default destination, use the JmsTemplate’s send(MessageCreator msgCreator) method variant. As the method signature suggests, it does not take any parameter for destination—the message is destined to go to a default destination. This default destination, however, needs to be wired in the config file.

In order to create a destination in the config file, we need to use a concrete implementation of javax.jms.Destination class—usually provided by the JMS providers. In the case of ActiveMQ, the destination is org.apache.activemq.command.ActiveMQTopic which is declared as a bean first here:

<bean id="defaultDestination" class="org.apache.activemq.command.ActiveMQTopic">
  <constructor-arg value="topic.DEFAULT_STUDENTS" />
</bean>

Then add a property defaultDestination in our JmsTemplate, making the reference point to the above destination:

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="defaultDestination" ref="defaultDestination" />
</bean>

Now that the moving parts are glued together, see the following publishing code snippet. Note that the send method does not have any reference to the destination.

public void publishToDefautDestination(final Student s) {
  getDefaultDestinationJmsTemplate().send(new MessageCreator() {
    @Override
    public Message createMessage(Session session) throws JMSException {
      ....
    }
  });
}

Declaring a Topic

By default, the JmsTemplate assumes that your messaging mode is point-to-point and hence the destination to be a Queue. However, if you wish to change this mode to pub/sub, all you are required to do is wire in a property called pubSubDomain, setting it to true.

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  ...
  <property name="pubSubDomain" value="true" />
</bean>

This way, we are expecting the messages to be published onto to a Topic. Remember to create the queues or topic in the config as shown here (for ActiveMQ):

<!- Queue-->
<bean id="defaultDestination" 
  class="org.apache.activemq.command.ActiveMQQueue">
  <constructor-arg value="queue.STUDENTS" />
</bean>

<!-- Topic -->
<bean id="defaultDestination" 
  class="org.apache.activemq.command.ActiveMQTopic">
  <constructor-arg value="topic.STUDENTS" />
</bean>

Receiving Messages

Using JmsTemplate makes consuming the messages simple. However, a bit of complexity arises when the reception modes are considered. The two modes in which you can receive messages are synchronous and asynchronous modes.

In synchronous mode, we will not be able to process any other actions until and unless we receive at least one message from the server. The thread that calls the receive method will not return, but waits indefinitely to pick up the message. As this is more of a single-theaded nature, I recommend not use this mode unless you have a strong case. Should you have no alternatives other than using the synchronous receive method, use it at least by setting a timeout so the waiting thread can gracefully exit after the timeout.

In the asynchronous mode, the client will let the provider know that it would be interested in receiving the messages from a destination. When a message arrives at that given destination, the provider checks the list of clients interested in that message and sends the message to those list of clients.

We use JmsTemplate’s receive method to consume messages in this fashion.

Receiving Messages Synchronously

The receive method on the templace instance takes in a destination so it can start consuming from there. The JobsReceiver shown here connects to a JMS provider and tries to receive messages (the instance has been injected with a JmsTemplate object).

public class JobsReceiver {
  private JmsTemplate jmsTemplate = null;
  private String destinationName = null;

  public void receiveMessages() {
    Message msg = getJmsTemplate().receive(destinationName);
    System.out.println("Job Received: " + msg);
  }
  ...
}

The receive method waits until the destination holds at least a message. As explained earlier, the receive method is a blocking call, which may waste CPU cycles if no message exists in the queue. So, use the receiveTimeout variable with the appropriate value. The receiveTimeout is an attribute on JmsTemplate that needs to be declared in config file, which is shown here:

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="receiveTimeout" value="2000" />
  ...
</bean>

In the above snippet, the JobsReceiver will timeout after two seconds if it does not receive any messages in that time period.

We can also receive messages from a defaultDestination. As we did earlier, what we have to do is wire the jmsTemplate with a defaultDestination property. Simple!

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="defaultDestination" value="topic.DEFAULT_JOBS" />
  ...
</bean>

Receiving messages asynchronously is a bit different, let’s see how we can do that.

Receiving Messages Asynchronously

In order to receive the messages asynchronously, we have to do a couple of things:

  • Create a class that implements the MessageListener interface.

  • Wire a Spring JMS Container in your spring beans XML file with a reference to your listener. We will talk about Containers in a minute.

So, the asynchronous client must implement JMS API’s interface MessageListener. This interface has one method called onMessage that must be implemented by your class. The BookOrderMessageListener, for example, is a simple class that implements the MessageListener. The method does not do much except print out the message to the console.

public class BookOrderMessageListener implements MessageListener{
  @Override
  public void onMessage(Message msg) {
    System.out.println("Book order received:"+msg.toString());
 }
}

The second part is the task of wiring the containers.

Don’t confuse these containers with the ApplicationContext or BeanFactory containers.

These containers are utility classes provided by the framework used in clients that are destined to receive messages. The containers are simple yet powerful classes that hide away all the complexities of connections and sessions. They are responsible for fetching the data from the JMS Provider and pushing it to your listener. They do this by having a reference to ConnectionFactory (and hence JMS Provider) and a reference to our listener class.

Now, let’s looks at the workings in detail.

Wire up the container and the listener as shown in the following code snippet. Define an instance of DefaultMessageListenerContainer and inject it with a connectionFactory, destination, and messageListener instances.

Note that the messageListener class refers to our listener class.

<bean id="bookOrderListener" 
  class="com.madhusudhan.jscore.jms.async.BookOrderMessageListener"/>

<bean id="defaultListenerContainer"
  class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="destination" ref="defaultDestination" />
  <property name="messageListener" ref="bookOrderListener" />
</bean>

Once the wiring is done, fire up the client, which loads up the above beans. It would start up the messageListener instance, which waits to receive messages from the JMS server.

Publish a message onto the destination and we can see that message popping up at the messageListener client.

Spring Message Containers

We have seen the usage of DefaultMessageListenerContainer in the previous section. Spring provides three different types of containers for receiving messages asynchronously, including the DefaultMessageListenerContainer. The other two are SimpleMessageListenerContainer and ServerSessionMessageListenerContainer.

The SimpleMessageListenerContainer is basically the simplest of all and is not recommended for production use. On the other hand, ServerSessionMessageListenerContainer is one level higher than DefaultMessageListenerContainer in complexity as well as features. This works if we want to work with JMS sessions directly. It is also used in the situation where XA transactions are required.

The DefaultMessageListenerContainer is well-suited for most of the applications and does allow you to participate in external transactions. Obviously choose the appropriate one based on your application’s requirement.

Message Converters

One of the requirements when publishing a message is to convert your domain object into five predefined JMS message types. We cannot simply publish or receive domain objects such as Trade or Order, even if they are properly serialized Java Objects. So, if you wish to publish your domain objects, you need to convert them into the appropriate JMS Message type.

See the following publish method shown here:

public void publish(final Student s) {
  getJmsTemplate().send(getDestinationName(), new MessageCreator() {
    @Override
    public Message createMessage(Session session) throws JMSException {
      TextMessage m = session.createTextMessage();
      m.setText("Enrolled Student: " + s.toString());
      return m;
      }
    });
  }
  ...
}

Did you notice that in order to send a message, we need to work with a session (to create appropriate object) and message creators (which provides us the session)? It’s not that clean, is it?

The publish method can be enhanced to lose its wrinkles and look smart as shown—with the help of converters. See the enhanced method shown here:

public void publish(final Student student) {
  jmsTemplate.convertAndSend(destinationName, student);
}

The convertAndSend method will hide away all the extra boiler plate code. We can acheive this goal by using Spring’s MessageConverters. They do the conversions easily without us having to sweat it anymore. Once the converter is plugged into the JmsTemplate, we do have to worry about the conversions. Of course, we have to write one of the converters to begin with though!

Let’s see how the converters work.

First, we should create a class that implements the MessageConverter interface. This interface has two methods: fromMessage and toMesage methods. As the names indicate, we code these methods either to convert a JMS message to a domain object or vice versa.

The following code shows a typical converter used for Account objects:

public class AccountConveter implements MessageConverter {
  @Override
  public Object fromMessage(Message msg) throws JMSException,
                                 MessageConversionException {
    Account t = (Account) ((ObjectMessage) msg).getObject();
    System.out.println("fromMessage: " + msg.toString());
    return t;
  }

  @Override
  public Message toMessage(Object obj, Session session) throws JMSException,
                                                MessageConversionException {
    ObjectMessage objMsg = session.createObjectMessage();
    objMsg.setObject((Account) obj);
    System.out.println("toMessage: " + objMsg.toString());
    return objMsg;
  }
}

In the fromMessage method, the Account object is grabbed from JMS ObjectMessage and converted to the actual domain object. Whereas, in the toMessage method, we use the passed-in session object to create an ObjectMessage and push the domain object by using setObject method.

Now that we have created an Account converter, the next step is to wire it into the JmsTemplate object.

<bean id="accountConverter" 
  class="com.madhusudhan.jscore.jms.convert.AccountConverter"/>
      
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="messageConverter" ref="accountConverter"/>
  ....
</bean>

In the publisher and receiver code, you need to change the send and receive methods to convertAndSend() and receiveAndConvert() so the converter bean gets used at publishing and receiving end.

//Publisher
public void publish(final Student student) {
  jmsTemplate.convertAndSend(destinationName, student);
}

//Receiver
public void receive() {
  Object o = jmsTemplate.receiveAndConvert(destinationName);
}

Whenever we publish an Account message, the jmsTemplate uses the converter to convert the Account to ObjectMessage. Similarly when receiving, the template calls the converter to do the reverse conversion. This way, you write the converter once and use it everywhere and at all times.

Summary

We have seen Java Messaging in action in this chapter. We briefly touched the subject of JMS and delved into using Spring’s JmsTemplate class. We learned how we can publish our messages using the template class. We also saw how we can receive messages synchronously and asynchronously using Spring’s framework classes called Message Containers. Lastly, we touched on the converters that would convert JMS message types to our business domain types and take away some more boilerplate code.

The next chapter deals with persistence and retrieval of Data, using Spring’s JDBC and Hibernate support.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.150.231