Creating a Message-Driven Bean

The setMessageDrivenContext() method can throw EJBException if there is a container or system level error of some kind. See the section called “Handling Exceptions” for more details. What follows is an example 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 Message-driven bean 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 Message-driven 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 Message-driven beans is insufficient to handle the number of incoming messages.

Therefore, the life of a Message-driven bean instance could be very long and, in this case, it makes sense to adopt an approach where you retain state (such as an open database connection) across the handling of several messages. However, the container may create and destroy instances to service every incoming message. If this is the case, this approach is no longer efficient. Check your vendor's documentation for details on how your EJB server handles Message-driven bean instances in the method-ready pool.

Message-driven bean 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. Message-driven beans are always single-threaded objects.

The Demise of the Bean

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 queue or topic 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.

The Message-driven bean must provide a single onMessage() method, and this method should not throw runtime exceptions. 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, Message-driven bean 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, this means that you cannot ascertain or control when the bean will run.

Handling Exceptions

The Message-driven bean 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 Message-driven bean 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 Message-driven bean 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.

Container- and Bean-Managed Transactions

The analysis of container- 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 Message-driven bean, 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 other reason for using bean-managed transactions, such as creating and sending a series of messages.

A Message-driven bean can be designed with either bean-managed transactions or with container-managed transactions, but both cannot be used in the same bean.

The following methods in the javax.ejb.MessageDrivenContext (all inherited from javax.ejb.EJBContext) can be used with transactions.

public UserTransaction getUserTransaction() throws
java.lang.IllegalStateException

The UserTransaction interface methods can be used to demarcate transactions.

The getUserTransaction() method can only be called if the Message-driven bean is using bean-managed transactions. An attempt to use this method from a bean using container-managed transactions will cause a java.lang.IllegalStateException to be thrown.

Both the methods setRollbackOnly() and getRollbackOnly() can only be used with container-managed transactions. This time, the IllegalStateException will be thrown if they are utilized in the context of a bean-managed transaction.

public void setRollbackOnly() throws java.lang.IllegalStateException

Typically, you use setRollbackOnly() after an exception or error of some kind to mark the current transaction to be rolled back.

public boolean getRollbackOnly() throws java.lang.IllegalStateException

The getRollbackOnly() method returns true if the transaction has been marked for rollback; otherwise, it returns false. You usually call this method after an exception has been caught to see if there is any point in continuing working on the current transaction.

Message Acknowledgment

With Message-driven beans, the container handles message acknowledgement. The default being AUTO_ACKNOWLEDGE.

If you use container-managed transactions, you have no control over the message acknowledgement; it is done automatically as part of the transaction commit.

With bean-managed transactions, you can specify DUPS_OK_ACKNOWLEDGE as an alternative to the default. 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.

<transaction-type>Bean</transaction-type>
<acknowledge-mode>Dups-ok-acknowledge</acknowledge-mode>

JMS Message Selectors

JMS message selectors was covered in detail on Day 9. A message selector is a string containing an SQL conditional expression. Only JMS message header values and message properties can be specified in the message selector.

With Message-driven beans, the selector is specified at deployment time.

The message selector is added to the screen in the Deployment Tool (see Figure 10.3). In the example shown, the bean will handle only messages that have a JMSPriority greater than the default of 4.

Figure 10.3. Deployment Tool screen showing the setting of message selectors.


The deployment descriptor is updated to include the message-selector tag.

<message-selector>JMSPriority &gt;4</message-selector>

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

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