What's New in JMS 2.0: Ease of Use


Continuing our Java EE 7 series, Oracle's Nigel Deakin shows how to new ease-of-use features enable you to write fewer lines of code. Reprinted with permission from the Oracle Technology Network, Oracle Corporation.
This article, which is the first article in a two-part series, assumes a basic familiarity with Java Message Service (JMS) 1.1 and introduces some of the new ease-of-use features in JMS 2.0. In Part Two, we will look at new messaging features.
JMS 2.0, which was released in April 2013, is the first update to the JMS specification since version 1.1 was released in 2002. One might think that an API that has remained unchanged for so long has grown moribund and unused. However, if you judge the success of an API standard by the number of different implementations, JMS is one of the most successful APIs around.
In JMS 2.0, the emphasis has been on catching up with the ease-of-use improvements that have been made to other enterprise Java technologies. While technologies such as Enterprise JavaBeans or Java persistence are now much simpler to use than they were a decade ago, JMS had remained unchanged with a successful, but rather verbose, API.
The single biggest change in JMS 2.0 is the introduction of a new API for sending and receiving messages that reduces the amount of code a developer must write. For applications that run in a Java EE application server, the new API also supports resource injection. This allows the application server to take care of the creation and management of JMS objects, simplifying the application even further.
JMS 2.0 is part of the Java EE 7 platform and can be used in Java EE Web or EJB applications, or it can be used standalone in a Java SE environment. As I explain below, some of the features described here are available only in a standalone environment while others are available only in Java EE Web or EJB applications.
Simplified API
The new API is known as the simplified API. As the name suggests, it is intended to be simpler and easier to use than the existing JMS 1.1 API, which is (rather predictably) now referred to as the classic API.
The simplified API consists of three new interfaces: JMSContext, JMSProducer, and JMSConsumer:
  • JMSContext replaces the separate Connection and Session objects in the classic API with a single object.
  • JMSProducer is a lightweight replacement for the MessageProducer object in the classic API. It allows message delivery options, headers, and properties to be configured using method chaining (sometimes known as a builder pattern).
  • JMSConsumer replaces the MessageConsumer object in the classic API and is used in a similar way.
Developers now have a choice as to whether to use the classic API (the familiar Connection, Session, MessageProducer, andMessageConsumer objects of JMS 1.1) or the simplified API (the JMSContext, JMSProducer, and JMSConsumer objects introduced in JMS 2.0).
The simplified API offers all the features of the classic API plus some additional features. The classic API is not deprecated and will remain part of JMS indefinitely.
Using the Simplified API to Send a Message
The JMS 1.1 classic API has been in use for over a decade and has proven its usefulness. In what ways is the JMS 2.0 simplified API better? The JMS 2.0 simplified API requires less code.
Listing 1 below shows a simple example that uses the classic API to send a single text message.
Listing 1

public void sendMessageJMS11(ConnectionFactory connectionFactory, Queue queueString text) {
   try {
      Connection connection = connectionFactory.createConnection();
      try {
         Session session =connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
         MessageProducer messageProducer = session.createProducer(queue);
         TextMessage textMessage = session.createTextMessage(text);
         messageProducer.send(textMessage);
      } finally {
         connection.close();
      }
   } catch (JMSException ex) {
      // handle exception (details omitted)
   }
}

Now compare Listing 1 to Listing 2, which shows how we might do exactly the same thing using the simplified API in JMS 2.0:
Listing 2

public void sendMessageJMS20(ConnectionFactory connectionFactory, Queue queue, 
String text) {
   try (JMSContext context = connectionFactory.createContext();){
      context.createProducer().send(queue, text);
   } catch (JMSRuntimeException ex) {
      // handle exception (details omitted)
   }
}


As you can see, the amount of code we have to write is significantly reduced. Let's look at this in more detail.
  • Instead of creating separate Connection and Session objects, we create a single JMSContext object.
  • The JMS 1.1 version used a finally block to call close on the Connection after use. In JMS 2.0, the JMSContext object also has a close method that needs to be called after use. However, there's no need to explicitly call close from your code.JMSContext implements the Java SE 7 java.lang.AutoCloseable interface. This means that if we create the JMSContext in atry-with-resources block (also a new feature of Java SE 7), the close method will be called automatically at the end of the block without the need to explicitly add it to your code.

    In fact, all JMS interfaces that have a close method have been extended to implement the java.lang.AutoCloseable interface, so they can all be used in try-with-resources blocks. This includes the Connection and Session interfaces as well asJMSContext. So even if you're using the classic API, you can still benefit from this feature. Note that because of this change, JMS 2.0 can be used only with Java SE 7.
  • When the JMS 1.1 version created the Session object, it passed in the parameters (false and Session.AUTO_ACKNOWLEDGE) to specify that we want to create a non-transacted session in which any received messages will be acknowledged automatically. In JMS 2.0, this is the default (for Java SE applications), so we don't need to specify any parameters.

    If we wanted to specify one of the other session modes (local transaction, CLIENT_ACKNOWLEDGE, or DUPS_OK_ACKNOWLEDGE), we would pass in just a single parameter rather than two.
  • There's no need to create a TextMessage object and set its body to be the specified string. Instead, we simply pass the string into the send method. The JMS provider will automatically create a TextMessage and set its body to the supplied string.
  • The JMS 1.1 example provided a catch block for the JMSException that almost all methods throw. The JMS 2.0 simplified API example has a similar block, but it catches a JMSRuntimeException instead.

    One feature of the simplified API is that its methods do not declare checked exceptions. If an error condition is encountered, aJMSRuntimeException is thrown. This new exception is a subclass of RuntimeException, which means it does not need to be explicitly caught by the calling method or declared in its throws clause. This contrasts with the classic API, in which almost every method is declared to throw a JMSException that the calling method must either catch or throw itself.
Both Listing 1 and Listing 2 show the ConnectionFactory and Queue objects being passed in as parameters. The way that these are obtained hasn't changed, so we won't cover this here or in the other listings in this article. Typically, they would be obtained by a JNDI lookup from a JNDI repository.

Using the Simplified API to Receive Messages Synchronously

Listing 3 shows a simple example that uses JMS 1.1 to receive a single TextMessage synchronously and extract its text.
Listing 3

public String receiveMessageJMS11(ConnectionFactory connectionFactory,Queue queue){
   String body=null;
   try {
      Connection connection = connectionFactory.createConnection();
      try {
         Session session =connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
         MessageConsumer messageConsumer = session.createConsumer(queue);
         connection.start();
         TextMessage textMessage = TextMessage)messageConsumer.receive();
         body = textMessage.getText();
      } finally {
         connection.close();
      }
   } catch (JMSException ex) {
      // handle exception (details omitted)
   }
   return body;


Listing 4 shows how we might do exactly the same thing using the simplified API in JMS 2.0:
Listing 4

public String receiveMessageJMS20(
ConnectionFactory connectionFactory,Queue queue){
   String body=null;
   try (JMSContext context = connectionFactory.createContext();){
      JMSConsumer consumer = session.createConsumer(queue);
      body = consumer.receiveBody(String.class);
   } catch (JMSRuntimeException ex) {
      // handle exception (details omitted)
   }
   return body;
}


As with sending a message, the amount of code we have to write is reduced. Some of the reasons are the same as in the previous example:
  • Instead of creating separate Connection and Session objects, we create a single JMSContext object.
  • We can create the JMSContext in a try-with-resources block so that it will be closed automatically at the end of the block. This removes the need to call close.
  • We don't need to specify that we want received messages to be acknowledged automatically, because that is the default.
Also, there are two additional ways in which JMS 2.0 reduces the amount of code needed to receive a message:
  • Whereas in JMS 1.1 we need to call connection.start() to start delivery of messages to the consumer, in the JMS 2.0 simplified API we don't: the connection is automatically started. (You can disable this behavior if you need to.)
  • There's no need to receive a Message object, cast it to a TextMessage, and then call getText to extract the message body. Instead, we call receiveBody, which returns the message body directly.

0 comments:

Post a Comment