8.3. Designing Message-Driven Beans

Now that we've introduced you to JMS, let's learn about message-driven beans. Simply put, a message-driven bean is an asynchronous consumer of JMS messages. Message-driven beans are stateless, server-side, transaction-aware components that receive messages from a JMS topic or queue. Like stateless session beans, message-driven beans do not store conversational state. They do not have a home or remote interface, nor do they have any business methods.

Figure 8-6 shows how a client interacts with a message-driven bean. When a message is sent from the client to a topic or queue destination in the JMS server, the EJB container fetches a bean instance from the message-driven bean pool. The container reads the message from the destination and calls the bean's onMessage() method with the message as an argument. The onMessage() method can then act on the message, since this is the data from the client.

Figure 8-6. Message-Driven Bean, JMS, and EJB Container Architecture


Note that the EJB container acts as a message receiver. To do this, the container performs all the consumer-side steps we showed you in our earlier discussion of JMS message processing. First, the container connects to the JMS server and associates the bean with the same topic or queue destination that was used by the client. Next, it creates a TopicSubscriber or QueueReceiver to receive the message. Finally, the container registers the message listener and sets the message acknowledgment mode. The fact that the container does all this setup work is a major advantage for message-bean developers.

There is another reason why message-driven beans are appealing. Because a message-driven bean is stateless, bean pool instances can concurrently consume hundreds, even thousands of messages delivered to the bean. This makes message-driven beans highly scalable. Message-driven beans can also forward tasks to other beans when they receive messages and model business processes by accessing database resources.

Let's take you through all the steps you'll need to know to apply message-driven beans to an enterprise design. We'll begin with how to create a message-driven bean.

Message Bean Structure

A message-driven bean is very simple to implement. Figure 8-7 shows the approach for a message bean called MusicMessageBean. This bean class implements two interfaces: MessageListener and MessageDrivenBean. The MusicMessageBean class, therefore, must provide implementations for ejbCreate(), ejbRemove(), setMessageDrivenContext(), and onMessage(). (The ejbCreate() method is required for a message bean, but is not inherited from either interface.) Note that the container does not invoke these methods as a result of client calls through either a home or remote interface; message beans do not have home or remote interfaces like session and entity beans. In particular, the EJB container calls the onMessage() method in the bean when a message arrives at a specific topic or queue destination.

Figure 8-7. Class Diagram Showing the EJB Classes for a Message-Driven Bean


Now let's look at how we might apply these methods to the design of the MusicMessageBean, which reads data from a Music database. The idea is that clients send SQL select statements to the message bean, which the bean uses to access the database. Listing 8.3 shows the partial source code for MusicMessageBean.java.

Listing 8.3. MusicMessageBean.java
public class MusicMessageBean implements
    MessageListener, MessageDrivenBean {

  private Connection connect = null;
  private DataSource ds;
  public void
  setMessageDrivenContext(MessageDrivenContext mdc) { }

  public void ejbCreate() {
    String dbName = "java:comp/env/jdbc/MusicDB";
    try {
      InitialContext jndi = new InitialContext();
      ds = (DataSource)jndi.lookup(dbName);
      . . .
    } catch (Exception ex) { . . . }
  }

  public void ejbRemove() { }

  public void onMessage(Message message) {
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
      TextMessage msg = (TextMessage)message;
      String selectQuery = msg.getText();
      connect = ds.getConnection();
      stmt = connect.prepareStatement(selectQuery);
      rs = stmt.executeQuery();
      . . .
    } catch (Exception ex) { . . .
    } finally {
      if (connect != null) connect.close();
      . . .
    }
  }
}

In ejbCreate(), we obtain the DataSource object from the initial context. Inside onMessage(), we use the incoming message as an SQL select query String. A JDBC prepared statement uses this query command to read the database into a result set. Consistent with our other JDBC examples, we obtain a database connection only right before accessing the database, and release it in the finally block when we're finished.

The argument to setMessageDrivenContext() is analogous to the session and entity context classes.

Design Guideline

Note that receiving a message within a Message Bean is much simpler than using vanilla JMS. With JMS, we must connect to the message queue or topic, create the message receiver or subscriber, and register the object that implements MessageListener. With a Message Bean, the EJB container performs these steps for you.


Message Bean Life Cycle

It's important to understand the life cycle of a message-driven bean so that your bean is designed properly. Figure 8-8 is a sequence diagram that describes the life cycle of a message-driven bean.This is another way of looking at the state diagram that we showed you in Chapter 2 (see Figure 2-7 on page 28).

Figure 8-8. Message-Driven Bean Life Cycle


A message-driven bean has only two states: a Does Not Exist state and a Ready state. The container creates an instance of a message bean, which begins in the Does Not Exist state. To transition to the Ready state, the sequence diagram shows three steps. First, the container calls the Class.newInstance() method to create an instance of the bean. Second, it calls setMessageDrivenContext() in the bean with a context object. Third, the container calls the beans's ejbCreate() method. At this point, the bean is in the Ready state and is ready to receive messages.

The bean stays in the Ready state as long as the client sends messages. For each received message, the container calls onMessage() with the message as an argument. The container calls ejbRemove() when the container no longer requires the bean instance. This makes the bean leave the Ready state and return to the Does Not Exist state. Note that the container does not activate or passivate message beans since they are stateless.

The sequence diagram shows the client producer publishing a topic or sending a message to a queue destination. How is this set up for the message bean? The answer is that the container obtains the JMS specifications from the message bean's deployment descriptor. A message bean's deployment descriptor determines which receiver object (TopicSubscriber or QueueReceiver) the container will create to handle a message. The container will also use this receiver object to register itself (the container) as a MessageListener. (Note that in message-driven beans, the container intercepts and then forwards the message to the message bean. Thus, the container must be the registered listener for the JMS server.) We'll take a look at deployment descriptors for message-driven beans a little later in this chapter.

Back to life cycles. Why is it important to understand them? Let's return to our MusicMessageBean design for a moment. Recall that the ejbCreate() method creates the DataSource object for the connection. The DataSource reference (ds) is an instance variable. Inside onMessage(), we open and close the database connection each time the database is read. This helps scalability, since we do not tie up connection resources in the container between incoming messages. Also, we release the connection inside a finally block in case the system throws an exception while the connection is open.

Exceptions with Message Beans

Message-driven beans have different rules from session and entity beans regarding exceptions. Recall that application exceptions are nonfatal errors that are generally recoverable, whereas system exceptions are runtime errors that indicate a more serious problem. Methods in session and entity beans may throw either an application exception or a system exception because clients call these methods synchronously and can respond to thrown exceptions.

Message-driven beans, on the other hand, have no clients and respond to messages asynchronously. Application exceptions, therefore, are not allowed in message-driven beans because no client is known to the bean. The only exception that may be thrown from a message-driven bean is a system exception. The container handles system exceptions by removing the bean instance and rolling back any transactions started by the bean or by the container.

Here's an example of exception handling in the ejbCreate() method of our MusicMessageBean.

public void ejbCreate() {
  String dbName = "java:comp/env/jdbc/MusicDB";
  try {
    InitialContext jndi = new InitialContext();
    ds = (DataSource)jndi.lookup(dbName);
  } catch (Exception ex) {
    throw new EJBException("Cannot find DataSource: " +
      ex.getMessage());
    }
    . . .
}

If an error occurs from the JNDI lookup, we throw system exception EJBException from the catch handler with a descriptive error message.

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

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