Part One

What's New in JMS 2.0: Ease of Use - Part 2

 

    Using the Simplified API to Receive Messages Asynchronously

    The previous examples showed how to receive messages synchronously by calling a method that blocked until a message was received or a timeout occurred.

    If you need to receive messages asynchronously in a Java SE application, in JMS 1.1 you need to create a MessageConsumer object and then use the method setMessageListener to specify an object that implements the MessageListener interface. You then need to call connection.start() to start delivery of messages:

    MessageConsumer messageConsumer = session.createConsumer(queue);

    messageConsumer.setMessageListener(messageListener);

    connection.start();

    The JMS 2.0 simplified API code is similar. You need to create a JMSConsumer object and then use the method setMessageListenerto specify the object that implements the MessageListener interface. Message delivery is started automatically; there's no need to callstart.

    JMSConsumer consumer = context.createConsumer(queue);

    consumer.setMessageListener(messageListener);

    Note that if you need to receive messages asynchronously in a Java EE 7 Web or EJB application then, as with previous versions of Java EE, you need to use a message-driven bean rather the setMessageListener method.

    Injecting a JMSContext into a Java EE Application

    If you're writing a Java EE Web or EJB application, then using the JMS 2.0 simplified API is even easier than in Java SE. This is because you can now "inject" the JMSContext into your code and leave it to the application server to work out when to create it and when to close it.

    The following code is a fragment from a Java EE 7 session bean or servlet that injects a JMSContext and uses it to send a message:

     

    @Inject @JMSConnectionFactory(
    "jms/connectionFactory") private JMSContext context;
    
    @Resource(lookup = "jms/dataQueue") private Queue dataQueue;
    
    public void sendMessageJavaEE7(String body) {
       context.send(dataQueue, body);
    }
    

     

     

    As you can see, there is no code to create the JMSContext and no code to close it. Instead, we simply declare a field of typeJMSContext and add the annotations @Inject and @JMSConnectionFactory.

    The @Inject annotation tells the container to create the JMSContext when it is needed. The @JMSConnectionFactory annotation tells the container the JNDI name of the ConnectionFactory that it should use.

    If the injected JMSContext is used in a JTA transaction (whether container-managed or bean-managed), the JMSContext is considered to have transaction scope. This means that after the JTA transaction is committed, the JMSContext will be automatically closed.

    If the injected JMSContext is used when there is no JTA transaction, the JMSContext is considered to have request scope. This means that the JMSContext will be closed when the request comes to an end. The length of a request is defined in the Contexts and Dependency Injection (CDI) specification, and it typically relates to an HTTP request from a client or a message received by a message-driven bean.

    An injected JMSContext has some powerful features in addition to being created and closed automatically by the application server. The most important is that if a servlet calls a session bean, or one session bean calls another, and both use an injected JMSContext, then as long as the two injected JMSContext objects are defined in the same way (for example, they have the sameJMSConnectionFactory annotation), they will actually correspond to the same JMSContext object. This reduces the number of JMS connections used by the application.

    Other API Simplifications in JMS 2.0

    JMS 2.0 also provides several other simplifications.

    New Method to Extract the Body Directly from a Message

    A JMS message consists of three parts:

    • The message headers
    • The message properties
    • The message body

    The type of the body varies with the message type: the body of a TextMessage is a String. The body of a BytesMessage is a byte array, and so on.

    In JMS 1.1, the message body is obtained using methods specific to the message type, such as the getText method on TextMessage. However, when a message is received by an application, the JMS API always provides the message as a javax.jms.Message object, which needs to be cast to the appropriate subtype before the body can be obtained. This applies both when the message has been returned from a call to receive and when the message has been delivered asynchronously by a call to the onMessage method of aMessageListener.

    For example, if you used the receive method to receive a TextMessage, you'd need to cast the returned object from a Message to aTextMessage and then call the getText method:

     

    Message message = consumer.receive(1000); // returns a TextMessage
    
    String body = ((TextMessage) message).getText();
    

     

    JMS 2.0 has added a new method that makes it slightly simpler to extract the body of a message. This is the getBody method onMessage, and it is available to users of both the classic and simplified APIs. This method takes the expected body type as a parameter and does not require you to perform a cast on either the message or the body:

     

    Message message = consumer.receive(1000); // returns a TextMessage
    
    String body = message.getBody(String.class);
    

     

    Let's look at how getBody simplifies the code needed to obtain the body of other message types.

    If the message is a BytesMessage, JMS 1.1 provides several ways to extract the byte array from a BytesMessage. The simplest is to call the readBytes method on BytesMessage. This copies the bytes to the specified byte array.

    Here's an example of a MessageListener that receives a BytesMessage and obtains the body as a byte array:

     

    void onMessage(Message message){ // delivers a BytesMessage
       int bodyLength = ((BytesMessage)message).getBodyLength();
       byte[] bytes = new byte[bodyLength];
       int bytesCopied = ((BytesMessage)message).readBytes(bytes);
       ...
    

     

     

    In JMS 2.0, the getBody method makes this much simpler:

     

    void onMessage(Message message){ // delivers a BytesMessage
       byte[] bytes = message.getBody(byte[].class);
       ...
    

     

    If the message is an ObjectMessage, in JMS 1.1 you need to call the getObject method on ObjectMessage and then cast the returned Serializable to the expected body type:

     

    void onMessage(Message message){ // delivers an ObjectMessage
       MyObject body = (MyObject)((ObjectMessage) message).getObject();
       ...
    

     

     

    Note the need to perform two casts. You need to cast the message from Message to ObjectMessage so you can call getObject. This returns the body as a Serializable, which you then need to cast to the actual type.

    In JMS 2.0, you can do this without any casts:

     

    void onMessage(Message message){ // delivers an ObjectMessage
       MyObject body = message.getBody(MyObject.class);
       ...
    

     

     

    Finally, if the message is a MapMessage, the getBody method allows you to return the body as a Map:

     

    Message message = consumer.receive(1000); // returns a MapMessage
    Map body = message.getBody(Map.class);
    

     

     

    The one message type for which getBody cannot be used is StreamMessage. This is because the stream typically consists of multiple objects that the application should read individually.

    When using the getBody method, if the specified class does not match the body type, a MessageFormatException is thrown. A companion method, isBodyAssignableTo, has also been added to Message, and it can be used to test whether a subsequent call togetBody would be able to return the body of a particular Message object as a particular type. This is useful if more than one type of message is expected.

    Methods to Receive a Message Body Directly

    In JMS 1.1, an application consuming messages synchronously uses the receive(), receive(timeout), or receiveNoWait()methods on MessageConsumer.

    In JMS 2.0, applications using the simplified API can do this using the same methods on JMSConsumer.

    These methods return a Message object from which the message body can be obtained. The getBody method described previously provides an easy way to obtain the body from this object.

    Applications using the JMS 2.0 simplified API have an additional option. JMSConsumer provides three methods—receiveBody(class), receiveBody(class, timeout), and receiveBodyNoWait(class)—that will receive the next message synchronously and return its body. As with getBody, the expected class is passed in as a parameter.

    So instead of using the code in Listing 5 or Listing 6, the application can use the single line shown in Listing 7.

     

    Listing 5

    JMSConsumer consumer = ...
    Message message = consumer.receive(1000); // returns a TextMessage
    String body = ((TextMessage) message).getText();
    

    Listing 6

     

    JMSConsumer consumer = ...
    Message message = consumer.receive(1000); // returns a TextMessage
    String body = message.getBody(String.class);
    

    Listing 7

     

    JMSConsumer consumer = ...
    String body = consumer.receiveBody(String.class,1000);
    

    The receiveBody methods can be used to receive any type of message except for StreamMessage (for the same reason that this message type does not support getBody) and Message (since it has no body), as long as the class of the expected body is known in advance.

    These new methods have been added only to JMSConsumer. They have not been added to MessageConsumer. This means that this feature is available only to applications using the JMS 2.0 simplified API.

    Furthermore, these methods do not provide access to the message headers or properties (such as the JMSRedelivered message header field or the JMSXDeliveryCount message property), so they should be used only if the application has no need to access them.

    New Methods to Create a Session

    In JMS 1.1, the following method on a javax.jms.Connection was used to create a javax.jms.Session, where the transacted parameter needed to be set to true or false and the acknowledgeMode parameter needed to be set toSession.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE, or Session.DUPS_OK_ACKNOWLEDGE.

     

    Session createSession(
      boolean transacted, int acknowledgeMode) throws JMSException
    

     

    This has always been a rather confusing method for two main reasons:

    • It used two arguments to define a single aspect of the session.
    • In a Java EE transaction, both arguments were ignored anyway.

    Let's consider these two problems in turn.

    Two Arguments to Define the Same Thing

    The first problem with the createSession method in JMS 1.1 is that it used two arguments to define what was, in practice, a single aspect of the session with four possibilities:

    • If the transacted parameter was set to false, the session was non-transacted and the acknowledgeMode parameter was used to specify which of three kinds of acknowledgment should be used when receiving messages.
    • If the transacted parameter was set to true, the acknowledgeMode parameter was ignored.

    In addition to being unnecessarily complicated, this led to code that was potentially misleading, because if the transacted parameter was set to false, the user still had to set the acknowledgeMode parameter to some value even if it is ignored. For example, the following code was perfectly valid:

     

    amb Session session = 
      connection.createSession(true,Session.AUTO_ACKNOWLEDGE);iguous
    

     

    In a Java EE Transaction, Both Arguments Are Ignored Anyway

    The second problem with the createSession method in JMS 1.1 is that in a Java EE Web or EJB application, if there is a current JTA transaction (as there is by default), both parameters to createSession are ignored anyway. However, since the API forced developers to specify two parameters, which led to highly misleading code, a user writing an EJB bean might write the following code in not realizing that the session would actually use the EJB's container-managed JTA transaction.

     

    Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
    

     

    To address these problems, two new methods with the name createSession have been added to javax.jms.Connection. One has a single parameter and one has no parameters at all. We'll discuss these in turn.

    JMS 2.0: createSession with One Parameter

    In JMS 2.0, a second createSession method has been added to javax.jms.Connection. This has a single parameter,sessionMode:

     

    Session createSession(int sessionMode) throws JMSException
    

     

    In a normal Java SE environment, sessionMode can be set to Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE,Session.DUPS_OK_ACKNOWLEDGE, or Session.TRANSACTED. In a Java EE transaction, sessionMode is ignored.

    JMS 2.0: createSession with No Parameters

    In a Java EE transaction, even passing a single parameter to createSession is misleading because the parameter is ignored if there is a Java EE transaction. To allow users to write code that is less misleading, a third createSession method has been added tojavax.jms.Connection that has no parameters:

     

    Session createSession() throws JMSException
    

     

    This method is particularly intended for use in a Java EE transaction, where there is no point in specifying a session mode because it is ignored. However, this method can also be used in a normal Java SE environment, in which case it is equivalent to callingcreateSession(Session.AUTO_ACKNOWLEDGE).

    JMS 2.0: createSession with Two Parameters

    The existing method createSession(boolean transacted,int acknowledgeMode) can still be used and will remain part of the API indefinitely. However, developers are encouraged to use the single- or no-parameter versions of this method instead.

     

Pages

Chris Mayer

What do you think?

JAX Magazine - 2014 - 06 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions