Special Edition Using Enterprise JavaBeans 2.0
Creating a Message-Driven Bean
Creating a message-driven bean is not that complicated. They really are much easier to create than session or entity beans because you don't have to worry about creating a home or a component interface for them. To create a message-driven bean, your class must implement two required interfaces:
The MessageDrivenBean Interface
The first interface that your message-driven bean must implement is the javax.ejb.MessageDrivenBean interface. Table 11.1 lists the methods that are part of the MessageDrivenBean interface. Table 11.1. Methods of the javax.ejb.MessageDrivenBean Interface
The ejbRemove method will be called on a message-driven bean just before the instance is about to be removed by the container. The bean should release any resources that it is holding. The resources could be JDBC connections, a JavaMail session, or other finite resources that need to be cleaned up and released.
Note The message-driven bean does not have to clean up any resources related to the JMS destination it's listening on. The container will handle those responsibilities.
The setMessageDrivenContext method takes a single argument, which is an object that implements the javax.ejb.MessageDrivenContext interface. This object provides access to the runtime message-driven context that the container associates with each message-driven bean. The MessageDrivenContext interface extends the javax.ejb.EJBContext and therefore provides access to security and transactional properties and methods. Table 11.2 describes the methods that are available through the MessageDrivenContext instance passed to the message-driven bean instance. Table 11.2. Methods of the MessageDrivenContext Interface
The MessageDrivenContext interface doesn't define any new methods itself. The methods displayed in Table 11.2 are inherited from the EJBContext interface. Future versions of this interface might define more methods that are specific to the message-driven bean context.
Caution Because the message-driven bean doesn't have a home interface, calling the getEJBHome or getEJBLocalHome method on a message-driven bean will throw a java.lang. IllegalStateException . It's only there because the specific context classes for the three enterprise beans all extend EJBContext .
Note There are several methods in the EJBContext interface that have been deprecated. Those methods are not listed in Table 11.2. See Appendix A for a complete listing of the deprecated methods in the EJBContext interface.
The JMS MessageListener Interface
The second required interface is the javax.jms.MessageListener interface. All message-driven beans must also implement this interface. This is the same interface that regular JMS message consumers must implement also. The MessageListener interface defines a single method that must be implemented: public void onMessage(javax.jms.Message msg); The container calls this method when a message arrives at the JMS destination and the bean instance should service it. The onMessage method is the method where your business logic should go. Obviously, you can have other public and private methods in your message-driven bean and call those from the onMessage method, but it all starts from here. The onMessage method contains a single argument, which is the javax.jms.Message that the container is asking the bean instance to handle.
Note Remember that javax.jms.Message is an interface, and several JMS message types implement this interface. If you are not sure which message type to expect, you can determine it programmatically using the instanceof operator.
The onMessage method should not be declared final or static. It must be declared public and have a void return type. It must also not throw any application or runtime exceptions. If something happens that would normally cause one of these exceptions, you should just catch the exception, log the information, and return. The EJB 2.0 Specification supports only JMS messaging, so all message-driven beans currently are JMS message-driven beans, to be precise. This is why the requirement to implement the javax.jms.MessageListener interface is an absolute one. When other messaging types are supported by the specification, you'll have choices other than javax.jms.MessageListener for your message-driven beans. Creating the Message-Driven Bean Class
The main work in creating the actual message-driven bean class is ensuring that you have implemented the two required interfaces and that you provide the business logic when the onMessage method is called. The rest of the work for creating the message-driven bean class is done during deployment. In Chapter 10, "Java Message Service," we created a class called AuctionNotificationConsumer in Listing 10.2. We mentioned in that section that we would eventually replace this consumer with a message-driven bean. We'll show an example of using a message-driven bean to listen on a JMS Queue for messages and then send an e-mail message using an e-mail service that we'll build later. We will be developing the details of the e-mail service and some other common services for an application later in Chapter 21, "Horizontal Services." Listing 11.1 shows the equivalent of the class from Chapter 10 now implemented as a message-driven bean. Listing 11.1 AuctionNotificationConsumer from Chapter 10 Implemented as a Message-Driven Bean
/** * Title: AuctionNotificationMessageBean * Description: The Message-driven bean gets messages from a Queue * and delegates to the horizontal service. */ package com.que.ejb20.notification; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.ObjectMessage; import javax.jms.MessageListener; import com.que.ejb20.services.email.Emailable; import com.que.ejb20.services.email.EmailService; import com.que.ejb20.services.email.EmailException; public class AuctionNotificationMessageBean implements MessageDrivenBean, MessageListener { private MessageDrivenContext ctx = null; // Default Constructor public AuctionNotificationMessageBean() { super(); } // This is where the real work happens public void onMessage( Message jmsMessage ) { if ( jmsMessage instanceof ObjectMessage) { try { Object obj = ((ObjectMessage)jmsMessage).getObject(); if ( obj instanceof Emailable ) { sendEmail( (Emailable)obj ); } }catch( JMSException ex ) { ex.printStackTrace(); } } } // Delegate to the horizontal service private void sendEmail( Emailable emailableMsg ){ try{ EmailService.sendEmail( emailableMsg ); }catch( EmailException ex ){ // Just print out the exception and move on ex.printStackTrace(); } } // Associate the private reference with this context so // that this bean can use the context if neccessary public void setMessageDrivenContext( MessageDrivenContext ctx ){ this.ctx = ctx; } public void ejbCreate(){ // This method is required, but you // don't have to do anything with it } public void ejbRemove(){ // This method is required, but you // are not required to do anything } } The main difference you should see between Listing 11.1 and Listing 10.2 from Chapter 10 is that you don't have to worry about getting connected to the JMS-administered objects. All you need to worry about is implementing the onMessage method and performing the business logic correctly. This is nice and in line with the EJB architecture because it allows the bean provider to focus more on the business logic. In the onMessage in Listing 11.1, the business logic is simply to ensure that the message is of the correct type and then to call the e-mail horizontal service. The other thing to notice from Listing 11.1 is that, even though you might not want to do anything with the ejbCreate or ejbRemove methods, you must still implement them in your class. There are some general restrictions for the message-driven bean class. The following list summarizes the rules that you must follow when creating your message-driven bean classes:
You are allowed to declare superclasses and subclasses for the message-driven bean. If you do use these, you are allowed to implement the setMessageDrivenContext or ejbRemove methods in a parent class so that all the subclasses can just inherit those implementations . Listing 11.2 illustrates an abstract implementation for a message-driven bean. Subclasses only need to provide an implementation for the onMessage method when extending this class. Listing 11.2 An Abstract Implementation that Message-Driven Beans Can Extend
/** * Title: AbstractMessageDrivenBean * Description: An abstract implementation for a MessageDrivenBean. Subclasses * only need to implement the onMessage method */ package com.que.ejb20.notification; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.jms.Message; import javax.jms.MessageListener; import com.que.ejb20.services.email.Emailable; import com.que.ejb20.services.email.EmailService; import com.que.ejb20.services.email.EmailException; abstract public class AbstractMessageDrivenBean implements MessageDrivenBean, MessageListener { private MessageDrivenContext ctx = null; // Associate the private reference with this context so // that this bean can use the context if necessary public void setMessageDrivenContext( MessageDrivenContext ctx ){ this.ctx = ctx; } public void ejbCreate(){ // This method is required, but you // don't have to do anything with it } public void ejbRemove(){ // This method is required, but you // are not required to do anything } // Concrete subclasses must provide an implementation for // the onMessage method abstract public void onMessage(); } If we modified the AuctionNotificationMessageBean class from Listing 11.1 to extend the abstract message-driven bean class in Listing 11.2, it would look like the class in Listing 11.3. Listing 11.3 NewAuctionNotificationMessageBean Extending the AbstractMessageDrivenBean
/** * Title: NewAuctionNotificationMessageBean * Description: The Message-driven bean gets messages from a Queue * and delegates to the horizontal service. */ package com.que.ejb20.notification; import javax.jms.Message; import javax.jms.ObjectMessage; import javax.jms.JMSException; import com.que.ejb20.services.email.Emailable; import com.que.ejb20.services.email.EmailService; import com.que.ejb20.services.email.EmailException; public class NewAuctionNotificationMessageBean extends AbstractMessageDrivenBean { // Default Constructor public NewAuctionNotificationMessageBean() { super(); } // This is where the real work happens public void onMessage( Message jmsMessage ) { if ( jmsMessage instanceof ObjectMessage) { try { Object obj = ((ObjectMessage)jmsMessage).getObject(); if ( obj instanceof Emailable ) { sendEmail( (Emailable)obj ); } }catch( JMSException ex ) { ex.printStackTrace(); } } } // Delegate to the horizontal service private void sendEmail( Emailable emailableMsg ){ try{ EmailService.sendEmail( emailableMsg ); }catch( EmailException ex ){ // Just print out the exception and move on ex.printStackTrace(); } } } The benefit of putting some of the behavior up in the parent class is that the concrete classes are a little smaller and easier to maintain. You also get the normal benefits that you get with inheritance and using default implementations. |