Life Cycle of a Message-Driven Bean

The EJB container controls the lifecycle of a Message-Driven bean. The Message-Driven bean instance lifecycle has three states, as shown in Figure 10.1:

  • Does Not Exist— The Message-Driven bean has not been instantiated yet or is awaiting garbage collection.

  • Method Ready Pool— A pool of Message-Driven bean instances, similar to the instance pool used for stateless session beans.

  • Processing a message— The Message-Driven bean has been instantiated and is handling a message.

Figure 10.1. The Message-Driven bean life cycle.


After constructing the new instance of the MDB object, the container invokes the following methods:

  • The bean's setMessageDrivenContext() method with a reference to its EJB context. The Message-Driven bean should store its MessageDrivenContext reference in an instance variable.

  • The bean's ejbCreate() method. The Message-Driven bean's ejbCreate() method takes no arguments and is invoked only once when the bean is first instantiated.

The MDB Context

The javax.ejb.MessageDrivenContext interface (see the class diagram in Figure 10.2) provides the MDB with access to its runtime context. This is similar to the SessionContext and EntityContext interfaces for Session and Entity beans.

Figure 10.2. The MessageDrivenContext class diagram.


Note that all the EJBContext methods are available to an MDB, but because the MDB does not have a local or remote interface, calls to getEJBHome() and getEJBLocalHome() will throw a java.lang.IllegalStateException.

Because MDBs are anonymous and run within the context of the container, and the container does not have a client security identity or role, calls to the getCallerPrincipal() and IsCallerInRole() methods will also cause an IllegalStateException.

Creating an MDB

The setMessageDrivenContext() method can throw an EJBException if there is a container or system level error of some kind. See the section titled “Handling Exceptions” for more details. What follows is a sample setMessageDrivenContext() method that saves its EJBContext and JNDI context:

private MessageDrivenContext mdbContext;
private Context jndiContext;
public void setMessageDrivenContext (MessageDrivenContext ctx) {
    mdbContext = ctx;
    try { jndiContext = new InitialContext();
    } catch (NamingException nameEx) {
        throw new EJBException(nameEx);
    }
}

After calling setMessageDrivenContext(), the container calls the bean's ejbCreate() method, which takes no parameters. You could use this method to allocate resources, such as a datasource, but in practice, this is usually done in the setMessageDrivenContext() method. Therefore, it is normal to find the ejbCreate() method empty.

This method is only invoked when the bean instance is first created.

public void ejbCreate () throws CreateException

After the ejbCreate() method has been called, the bean is placed in the method-ready pool.

Method-Ready Pool

The actual point at which MDB instances are created and placed in the method-ready pool is vendor specific. The vendor of an EJB server could design it to only create bean instances when they are required. Alternatively, when the EJB server is started, a number of instances may be placed in the method-ready pool awaiting the first message. Additional instances can be added to the pool when the number of beans is insufficient to handle the number of incoming messages.

MDB instances in the method-ready pool are available to consume incoming messages. Any available instance can be allocated to a message and, while processing the message, this particular bean instance is not available to consume other messages. A container can handle several messages concurrently by using a separate instance of the message bean for each message. Each separate instance obtains its own MessageDrivenContext from the container. After the message has been processed, the instance is available to consume other messages. MDBs are always single-threaded objects.

The Demise of an MDB

When the server decides to reduce the total size of the method-ready pool, a bean instance is removed from the pool and becomes available for garbage collection. At this point, the bean's ejbRemove() method is called.

You should use this method to close or deallocate resources stored in instance variables and set the instance variable to null.

public void ejbRemove()

The EJBException can be thrown by ejbRemove() to indicate a system-level error.

Following ejbRemove(), the bean is dereferenced and no longer available to handle messages. It will eventually be garbage collected.

NOTE

The ejbRemove() method may not be called if the Message-Driven bean instance throws an exception. This could result in resource leaks.


A Message-Driven bean must not define the finalize method to free up resources; do all the tidying up in ejbRemove().

Consuming Messages

When a message is received, the container finds a Message-Driven bean instance that is registered for that destination and calls the bean's onMessage() method.

public void onMessage(Message message)

This method has a single parameter that contains a single JMS message. The message will have a header, one or more properties (optional), and a message body (consisting of one of the five JMS message body types). JMS messages were covered in some detail on Day 9.

An MDB must provide a single onMessage() method. This method should not throw runtime exceptions and it must not have a throws clause as part of its method signature. The onMessage() holds the business logic of the bean. You can use helper methods and other EJBs to process the message.

Remember, MDB instances are triggered asynchronously; the business logic within the bean must reflect this. You must never presume any ordering to the messages received. Even if the system is implemented within the same JVM, the system vagaries can cause the scheduling of bean instances to be non-deterministic, which means that you cannot be certain when the bean will run.

Handling Exceptions

An MDB can encounter various exceptions or errors that prevent it from successfully completing. The following are examples of such exceptions:

  • Failure to obtain a database connection

  • A JNDI naming exception

  • A RemoteException from invocation of another EJB

  • An unexpected RuntimeException

A well-written MDB should never carelessly throw a RuntimeException. If a RuntimeException is not caught in onMessage() or any other bean class method, the container will simply discard the instance (it will transition it to the Does Not Exist state). In this case, the container will not call the ejbRemove() method, so a badly written bean method could cause resource leaks.

Obviously, you need a mechanism to tell the container that you have caught an unrecoverable error and die gracefully. To do this, you use exception layering. You catch the RuntimeException, free up resources, do any other appropriate processing and then throw an EJBException to the container. The container will then log the error, rollback any container-managed transactions, and discard the instance.

Because identical MDB instances are available, from the client perspective, the message bean continues to exist in the method-ready pool to handle further messages. Therefore, a single instance failure may not cause any disruption to the system.

MDB Transactions

The analysis of container-managed versus bean-managed transactions was covered as part of Day 8's material, reread this if you need to recap the benefits of either method of handling transactions. When designing your MDB you must decide whether the bean will demarcate the transactions programmatically (bean managed transactions), or if the transaction management is to be performed by the container. This is done by setting the transaction-type in the deployment descriptor.

<transaction-type>Container</transaction-type>

Use container-managed transactions unless you have some reason for using bean-managed transactions, such as sending and/or receiving a series of messages that together constitute an atomic processing action. An MDB can only use one of bean-managed or container-managed transactions.

Message Acknowledgment

By default the container handles message acknowledgement for MDBs. If you use container-managed transactions, you cannot change the default, as acknowledgement is handled as part of the transaction.

With bean-managed transactions, you can specify DUPS_OK_ACKNOWLEDGE as an alternative to the default (AUTO_ACKNOWLEDGE). To do this, set the acknowledge-mode element in the deployment descriptor. With DUPS_OK_ACKNOWLEDGE set, you can reduce the session overhead spent preventing delivery of duplicate messages, but only do this if receiving duplicate messages will not cause a problem with the business logic of your bean. MDBs cannot use the CLIENT_ACKNOWLEDGE option and should therefore not call the Message.acknowledge() method.

Using an MDB

MDBs provide an asynchronous interface to the EJB components of your applications. As such they are primarily used to implement so called back office functionality. Typically a session bean delegates functionality to an MDB to allow the client application to continue processing without waiting for a potentially slow operation to complete. A typical example may be a batch update of a database.

As the MDB is not accessible by the client it cannot return information directly to the client. The results of any operations performed by an MDB are typically retrived by the client using Session beans (or possibly Entity beans). An alternative approach to this client pull model is to implement a client push model where the MDB returns the results of the operation using JMS or JavaMail.

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

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