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