Working with message-driven beans Part 1 (EJB 3)

We’ll now build on our brief coverage of MDBs in topics 1 and 2 and explore MDBs in detail, why you should consider using them, and how to develop them. We’ll also discuss some best practices as well as pitfalls to avoid when developing MDBs.

Message-driven beans are EJB components designed to consume the asynchronous messages we’ve been discussing. Although MDBs are intended to handle many different kinds of messages (see the sidebar "JCA Connectors and Messaging"), we’ll primarily focus on MDBs that process JMS messages because most enterprise applications use JMS. From this perspective, you might ask why we’d need to employ EJBs to handle the task of consuming messages at all when we could use the code we just developed for the JMS message consumer. We’ll address this question next. We’ll develop a simple message consumer application using MDBs and show you how to use the @MessageDriven annotation. You’ll also learn more about the MessageListener interface, activation configuration properties, and the MDB lifecycle.

JCA connectors and messaging

Although by far JMS is the primary messaging provider for MDBs, as of EJB 2.1 it is not the only one. Thanks to the Java EE Connector Architecture (JCA), MDBs can receive messages from any enterprise information system (EIS), such as People-Soft HR or Oracle Manufacturing, not just MOMs that support JMS.

Suppose you have a legacy application that needs to send messages to an MDB. You can do this by implementing a JCA-compliant adapter/connector that includes support for message inflow contract. Once your JCA resource adapter or connector is deployed to a Java EE container, you can use the message inflow contract to have an asynchronous message delivered to an endpoint inside the container. A JCA endpoint is essentially the same as a JMS destination—it acts as a server proxy to an MDB (a message consumer/listener in JMS terms). As soon as a message arrives at the endpoint, the container triggers any registered MDBs listening to the endpoint and delivers the message to it.


For its part, the MDB implements a listener interface that is appropriate to the JCA connector/message type and passes activation configuration parameters to the JCA connector and registers as a listener to the JCA connector (we’ll discuss message listeners and activation configuration parameters shortly). JCA also enables MOM providers to integrate with Java EE containers in a standardized manner using a JCA-compliant connector or resource adapter.

Why use MDBs?

Given the less-than-stellar reputation of EJB 2, you might question the value of EJB 3 MDBs. The truth is, MDBs have enjoyed a reasonable degree of success even in the darkest hours of EJB. In this section, we’ll explain why you should take a serious look at MDBs.

Multithreading

Your business application may require multithreaded message consumers that can process messages concurrently. MDBs help you avoid complexity because they handle multithreading right out of the box, without any additional code. They manage incoming messages among multiple instances of beans (in a pool) that have no special multithreading code themselves. As soon as a new message reaches the destination, an MDB instance is retrieved from the pool to handle the message, as figure 4.7 shows. This is popularly known as MDB pooling, which you’ll learn about when we discuss the MDB lifecycle later in this topic.

 As soon as a message arrives at its destination, the container retrieves it and assigns a servicing MDB instance from the pool.

Figure 4.7 As soon as a message arrives at its destination, the container retrieves it and assigns a servicing MDB instance from the pool.

Simplified messaging code

In addition, MDBs relieve you from coding the mechanical aspects of processing messages—tasks such as looking up connection factories or destinations, creating connections, opening sessions, creating consumers, and attaching listeners. As you’ll see when we build the Turtle message consumer MDB, all these tasks are handled behind the scenes for you. In EJB 3, using sensible defaults for common circumstances eliminates most of the configuration. In the worst-case scenario, you’ll have to supply configuration information using simple annotations or through the deployment descriptor.

Starting message consumption

To start picking up messages from the shipping request queue, someone needs to invoke the appropriate method in your code. In a production environment, it is not clear how this will be accomplished. Starting message consumption through a user-driven manual process obviously is not desirable. In a server environment, almost every means of executing the method on server startup is highly system dependent, not to mention awkward. The same is true about stopping message receipt manually. On the other hand, registered MDBs would be bootstrapped or torn down gracefully by the container when the server is started or stopped.

We’ll continue consolidating these three points as we start investigating a real example of developing MDBs soon. Before we do that, though, let’s list the simple rules for developing an MDB.

Programming rules

Like all EJBs, MDBs are plain Java objects that follow a simple set of rules and sometimes have annotations. Don’t take these rules too seriously yet; simply note them in preparation for going through the code-intensive sections that follow.

■ The MDB class must directly (by using the implements keyword in the class declaration) or indirectly (through annotations or descriptors) implement a message listener interface.

■ The MDB class must be concrete. It cannot be either a final or an abstract class.

■ The MDB must be a POJO class and not a subclass of another MDB.

■ The MDB class must be declared public.

■ The bean class must have a no-argument constructor. If you don’t have any constructors in your Java class, the compiler will create a default constructor. The container uses this constructor to create a bean instance.

■ You cannot define a finalize method in the bean class. If any cleanup code is necessary, it should be defined in a method designated as PreDestroy.

■ You must implement the methods defined in the message listener interface. These methods must be public and cannot be static or final.

■ You must not throw the javax.rmi.RemoteException or any runtime exceptions. If a RuntimeException is thrown, the MDB instance is terminated.

We’ll apply these rules next in developing our example MDB.

Developing a message consumer with MDB

Let’s now explore developing an MDB by reworking the Turtle server JMS message consumer as an MDB. To make the code a bit more interesting, we’ll actually implement the processShippingRequest method mentioned in the JMS code. Listing 4.2 shows the MDB code that first retrieves shipping requests sent to the queue and then saves each request in the Turtle database table named shipping_request. Note that we’re using JDBC for simplicity’s sake and because it lets us demonstrate the MDB lifecycle methods for opening and closing JDBC connections. We recommend that you consider EJB 3 Java Persistence API (discussed in part 3) for persisting your data instead of using straight JDBC.

Listing 4.2 Turtle server shipping request processor MDB

Listing 4.2 Turtle server shipping request processor MDBListing 4.2 Turtle server shipping request processor MDB

The @MessageDriven annotation O identifies this object as an MDB and specifies the MDB configuration, including the fact that we are listening on the shipping request queue. Our code then marks this MDB as a JMS message listener Q. The onMessage method implements the message listener interface Q and processes incoming messages. A message-driven context is injected G and used inside the onMessage method Q to roll back transactions as needed. A database resource is injected Q. The lifecycle callbacks G open and close a connection derived from the database resource. Finally, the shared JDBC connection is used by the business logic G called in onMessage to save each shipping request into the database.

Next, we’ll examine the major MDB features by analyzing this code in greater detail, starting with the @MessageDriven annotation.

Using the @MessageDriven annotation

MDBs are one of the simplest kinds of EJBs to develop, and they support the smallest number of annotations. In fact, the @MessageDriven annotation and the @ActivationConfigProperty annotation nested inside it are the only MDB-specific annotations. The @MessageDriven annotation in our example represents what you’ll typically use most of the time. The annotation is defined as follows:

tmp24111_thumb

Notice that all three of the annotation’s arguments are optional. If you are a minimalist, you can keep the annotation as simple as this:

tmp24112_thumb

and leave any details to be added elsewhere, such as in the deployment descriptor.

The first element, name, specifies the name of the MDB—in our case, Shipping-RequestProcessor. If the name element is omitted, the code uses the name of the class, ShippingRequestProcessorMDB, in our example. The second parameter, messageListenerInterface, specifies which message listener the MDB implements. The last parameter, activationConfig, is used to specify listener-specific configuration properties. Let’s take a closer look at the two last parameters.

Implementing the MessageListener

An MDB implements a message listener interface for the very same reason our plain JMS consumer implemented the javax.jms.MessageListener interface. The container uses the listener interface to register the MDB with the message provider and to pass incoming messages by invoking implemented message listener methods. Using the messageListenerlnterface parameter of the @MessageDriven annotation is just one way to specify a message listener; we could have done the following instead:

Yet another option is to specify the listener interface through the deployment descriptor and leave this detail out of our code altogether. The approach you choose is largely a matter of taste. We prefer the second approach because it resembles our JMS example.

MDBs let you specify a message listener with relative flexibility, which is especially cool if you consider the following scenario: suppose that we decide to switch messaging technologies and use Java API for XML Messaging (JAXM) to send shipping requests instead of JMS. (JAXM is essentially a SOAP-based XML messaging API. For more information, visit http://java.sun.com/webservices/jaxm/.) Thanks to JCA support, we can use still use MDBs to receive shipping requests (see the sidebar "JCA Connectors and Messaging" to learn how this might be done). All we have to do is switch to the JAXM message listener interface, javax.jaxm. OneWayMessageListener, instead of using javax.jms.MessageListener. We can reuse most of the MDB code and configuration:

tmp24113_thumb

However you choose to specify the message listener, make sure you provide a valid implementation of all methods required by your message listener—especially when using the deployment descriptor approach, where there are no compile-time checks to watch your back. Next, let’s take a look at the last (but definitely not least) parameter of the @MessageDriven annotation: activationConfig.

Using ActivationConfigProperty

The activationConfig property of the @MessageDriven annotation allows you to provide messaging system-specific configuration information through an array

tmp24114_thumb

However, we chose to omit this parameter and specified the interface using the implements keyword:

tmp24115_thumbof ActivationConfigProperty instances. ActivationConfigProperty is defined as follows:

tmp24116_thumb

Each activation property is essentially a name-value pair that the underlying messaging provider understands and uses to set up the MDB. The best way to grasp how this works is through example. Here, we provide three of the most common JMS activation configuration properties: destinationType, connectionFactory-JndiName, and destinationName:

tmp24117_thumb

First, the destinationType property tells the container this JMS MDB is listening to a queue. If we were listening to a topic instead, the value could be specified as javax.jms.Topic. Next, connectionFactoryJndiName specifies the JNDI name of the connection factory that should be used to create JMS connections for the MDB. Finally, the destinationName parameter specifies that we are listening for messages arriving at a destination with the JNDI name of jms/ShippingRequestQueue.

There are a few other configuration properties for JMS that we’ll describe in the sections that follow. Visualizing what happens behind the scenes can help you remember these configuration properties. The container does something similar to our JMS message consumer setup steps (as shown in listing 4.2) to bootstrap the MDB. Most of the method parameters that we specify during those steps are made available as configuration properties in the MDB world.

Acknowledge Mode

Messages are not actually removed from the queue until the consumer acknowledges them. There are many "modes" through which messages can be acknowledged. By default, the acknowledge mode for the underlying JMS session is assumed to be AUTO_ACKNOWLEDGE, which means that the session acknowledged messages on our behalf in the background. This is the case for our example (since we omitted this property). All of the acknowledgment modes supported by JMS are listed in table 4.1. We could change the acknowledge mode to DUPS_OK_ACKNOWLEDGE (or any other acknowledge mode we discuss in table 4.1) using the following:

tmp24118_thumb

Table 4.1 JMS session acknowledge modes. For nontransacted sessions, you should choose the mode most appropriate for your project. In general, AUTO_ACKNOWLEDGE is the most common and convenient. The only other mode supported with MDB is DUPS_OK_ACKNOWLEDGE.

Acknowledgment Mode

Description

Supported with MDB

tmp24-119

The session automatically acknowledges receipt after a message has been received or is successfully processed.

YES

tmp24-120

You have to manually acknowledge the receipt of the message by calling the acknowledge method on the message.

NO

tmp24-121

The session can lazily acknowledge receipt of the message. This is similar to AUTO_ACKNOWLEDGE but useful when the application can handle delivery of duplicate messages and rigorous acknowledgment is not a requirement.

YES

tmp24-122

This is returned for transacted sessions if the Session.getAcknowledgeMode method is invoked.

NO

Next post:

Previous post: