EJB Message-Driven Beans
Pages: 1, 2, 3, 4, 5, 6, 7
The ReservationProcessor clients
In order to test the ReservationProcessor EJB, we need to develop two new client applications: one to send reservation messages and the other to consume ticket messages produced by the ReservationProcessor EJB.
The reservation message producer
The JmsClient_ReservationProducer is
designed to send 100 reservation requests very quickly. The speed with which
it sends these messages will force many MDB containers to use multiple
instances to process the reservation messages. The code for JmsClient_ReservationProducer looks like this:
import javax.jms.Message;
import javax.jms.MapMessage;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Queue;
import javax.jms.QueueSender;
import javax.jms.JMSException;
import javax.naming.InitalContext;
import java.util.Date;
import com.titan.processpayment.CreditCardDO;
public class JmsClient_ReservationProducer {
public static void main(String [] args) throws Exception {
InitialContext jndiContext = getInitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory)
jndiContext.lookup("QueueFactoryNameGoesHere");
Queue reservationQueue = (Queue)
jndiContext.lookup("QueueNameGoesHere");
QueueConnection connect = factory.createQueueConneciton();
QueueSession session =
connect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(reservationQueue);
Integer cruiseID = new Integer(1);
for(int i = 0; i < 100; i++){
MapMessage message = session.createMapMessage();
message.setStringProperty("MessageFormat","Version 3.4");
message.setInt("CruiseID",1);
message.setInt("CustomerID",i%10);
message.setInt("CabinID",i);
message.setDouble("Price", (double)1000+i);
// the card expires in about 30 days
Date expirationDate = new Date(System.currentTimeMillis()+43200000);
message.setString("CreditCardNum", "923830283029");
message.setLong("CreditCardExpDate", expirationDate.getTime());
message.setString("CreditCardType", CreditCardDO.MASTER_CARD);
sender.send(message);
}
connect.close();
}
public static InitialContext getInitialContext()
throws JMSException {
// create vendor-specific JNDI context here
}
}
You may have noticed that the JmsClient_ReservationProducer sets the CustomerID, CruiseID, and
CabinID as primitive int
values, but the ReservationProcessorBean reads
these values as java.lang.Integer types. This is
not a mistake. The MapMessage automatically
converts any primitive to its proper wrapper if that primitive is read using
MapMessage.getObject(). So, for example, a named
value that is loaded into a MapMessage using setInt() can be read as an Integer using getObject(). For
example, the following code sets a value as a primitive int and then accesses it as a java.long.Integer object:
MapMessage mapMsg = session.createMapMessage();
mapMsg.setInt("TheValue",3);
Integer myInteger = (Integer)mapMsg.getObject("TheValue");
if(myInteger.intValue() == 3 )
// this will always be true
The ticket message consumer
The JmsClient_TicketConsumer is
designed to consume all the ticket messages delivered by ReservationProcessor
EJB instances to the queue. It consumes the messages and prints out the
descriptions:
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Queue;
import javax.jms.QueueReceiver;
import javax.jms.JMSException;
import javax.naming.InitalContext;
import com.titan.travelagent.TicketDO;
public class JmsClient_TicketConsumer
implements javax.jms.MessageListener {
public static void main(String [] args) throws Exception {
new JmsClient_TicketConsumer();
while(true){Thread.sleep(10000);}
}
public JmsClient_TicketConsumer() throws Exception {
InitialContext jndiContext = getInitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory)
jndiContext.lookup("QueueFactoryNameGoesHere");
Queue ticketQueue = (Queue)jndiContext.lookup("QueueNameGoesHere");
QueueConnection connect = factory.createQueueConneciton();
QueueSession session =
connect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
QueueReceiver receiver = session.createReceiver(ticketQueue);
receiver.setMessageListener(this);
connect.start();
}
public void onMessage(Message message) {
try {
ObjectMessage objMsg = (ObjectMessage)message;
TicketDO ticket = (TicketDO)objMsg.getObject();
System.out.println("********************************");
System.out.println(ticket);
System.out.println("********************************");
} catch(JMSException jmsE) {
jmsE.printStackTrace();
}
}
public static InitialContext getInitialContext() throws JMSException {
// create vendor-specific JNDI context here
}
}
To make the ReservationProcessor EJB work with the two client
applications, JmsClient_ReservationProducer and
JmsClient_TicketConsumer, you must configure your
EJB container's JMS provider so that it has two queues: one for reservation
messages and another for ticket messages.
The Life Cycle of a Message-Driven Bean
|
Some vendors may not pool MDB instances, but may instead create and destroy instances with each new message. This is an implementation-specific decision that should not impact the specified life cycle of the stateless bean instance. |
Just as the entity and session beans have well-defined life cycles, so does the MDB bean. The MDB instance's life cycle has two states: Does Not Exist and Method-Ready Pool. The Method-Ready Pool is similar to the instance pool used for stateless session beans. Like stateless beans, MDBs define instance pooling in their life cycles.
Figure 13-4 illustrates the states and transitions that an MDB instance goes through in its lifetime.
|
Does Not Exist
When an MDB instance is in the Does Not Exist state, it is not an instance in the memory of the system. In other words, it has not been instantiated yet.
The Method-Ready Pool
MDB instances enter the Method-Ready Pool as the container needs them. When the EJB server is first started, it may create a number of MDB instances and enter them into the Method-Ready Pool. (The actual behavior of the server depends on the implementation.) When the number of MDB instances handling incoming messages is insufficient, more can be created and added to the pool.
Transitioning to the Method-Ready Pool
When an instance transitions from the Does Not Exist state to
the Method-Ready Pool, three operations are performed on it. First, the bean
instance is instantiated when the container invokes the Class.newInstance() method on the MDB class. Second, the
setMessageDrivenContext() method is invoked by the
container providing the MDB instance with a reference to its EJBContext. The MessageDrivenContext reference may be stored in an
instance field of the MDB.
Finally, the no-argument ejbCreate()
method is invoked by the container on the bean instance. The MDB has only one
ejbCreate() method, which takes no arguments. The
ejbCreate() method is invoked only once in the life
cycle of the MDB.
|
The duration of an MDB instance's life is assumed to be very long. However, some EJB servers may actually destroy and create instances with every new message, making this strategy less attractive. Consult your vendor's documentation for details on how your EJB server handles stateless instances. |
MDBs are not subject to activation, so they can maintain open
connections to resources for their entire life cycles.
The ejbRemove() method should close any open
resources before the MDB is evicted from memory at the end of its life cycle.
Life in the Method-Ready Pool
|
Related Reading
|
Once an instance is in the Method-Ready Pool, it is ready to
handle incoming messages. When a message is delivered to an MDB, it is
delegated to any available instance in the Method-Ready Pool. While the
instance is executing the request, it is unavailable to process other
messages. The MDB can handle many messages simultaneously, delegating the
responsibility of handling each message to a different MDB instance. When a
message is delegated to an instance by the container, the MDB instance's MessageDrivenContext changes to reflect the new
transaction context. Once the instance has finished, it is immediately
available to handle a new message.
Transitioning out of the Method-Ready Pool: The death of an MDB instance
Bean instances leave the Method-Ready Pool for the Does Not
Exist state when the server no longer needs them. This occurs when the server
decides to reduce the total size of the Method-Ready Pool by evicting one or
more instances from memory. The process begins by invoking the ejbRemove() method on the instance. At this time, the
bean instance should perform any cleanup operations, such as closing open
resources. The ejbRemove() method is invoked only
once in the life cycle of an MDB instance--when it is about to transition to
the Does Not Exist state. During the ejbRemove()
method, the MessageDrivenContext and access to the
JNDI ENC are still available to the bean instance. Following the execution of
the ejbRemove() method, the bean is dereferenced
and eventually garbage collected.
View catalog information for Enterprise JavaBeans, 3rd Edition
Return to ONJava.com.

