Developing Message-Driven Beans
An message-driven EJB is used to receive and process asynchronous
messages using JMS. Message-driven EJBs are never directly invoked by
other EJBs. However, they in turn can invoke methods of session and
entity beans and send JMS messages to be processed by other
message-driven EJBs. The topics listed below discuss development of
message-driven beans.
A D V E R T I S E M E N T
What are Message-Driven Beans?
Message-driven beans are server-side objects used only to process JMS
messages. These beans are stateless, in that each method invocation is
independent from the next. Unlike session and entity beans, message-driven
beans are not invoked by other beans or client applications. Instead a
message-driven bean responds to a JMS message.
Because message-driven beans are not invoked by other EJBs or clients,
these beans do not have interfaces. For each message-driven bean a single
method, onMessage, is defined to process a
JMS message. Although message-driven beans cannot be invoked by other EJBs,
they can in turn invoke other EJBs. Also, message-driven beans can send JMS
messages. As with the other types of EJBs, the EJB container is responsible
for managing the beans environment, including making enough instances
available for processing and message-acknowledgement.
Asynchronous and Concurrent Processing
A core feature of message-driven beans is the notion of asynchronous
processing. A client application can send a JMS message to execute a certain
business task. After the message has been sent, the client application can
continue right away and does not have to wait for the JMS message to be
received and processed. This is especially helpful when the business task is
complex, requires the use of entity (and session) beans, and takes a long
time to complete. In contrast, if the same client application were to use a
session bean to execute a certain business task, it would have to wait until
the session bean method completed and returned control to the client
application. The message fa�ade design pattern formalizes this use of
message-driven beans as an intermediary between client applications and
entity beans to achieve asynchrony. An example of this pattern is shown in
the
Message-Driven Bean Sample.
Another important feature of message-driven beans is that JMS messages
are processed concurrently. That is, although each bean instance handles a
message at a time, the EJB container takes care of creating enough bean
instances to handle the message load at a given moment. In WebLogic you can
set the initial number and max number of bean instances created by the
container. For more information, see the
@ejbgen:message-driven Annotation.
Because message-driven beans are stateless and processing of JMS messages
occurs in an asynchronous message, there is no guarantee that messages are
processed in the order they were sent. Therefore, sending multiple messages
such that one message is dependent on the successful processing of another
message might cause unexpected results. Instead, you should reconsider the
granularity of your business task such that one message can initiate its
execution, possibly by handling one piece of the task, and then sending a
JMS message to be processed by another message-driven bean for the remainder
of the business task.
Topics and Queues
A message-driven bean listens to a particular channel for JMS messages. There
are two types of channels, namely topics and queues. Topics implement the
publish-and-subscribe messaging model, in which a given message can be received
by many different subscribers, that is, many different message-driven bean
classes (not instances) listening to the same topic. In contrast, queues
implement the point-to-point messaging model, in which a given message is
received by exactly one message-driven bean class, even when multiple classes
are listening to this queue.
The Life Cycle of a Message-Driven
Bean
The EJB container is responsible for creating a pool of message-driven bean
instances. When it creates an instance, it calls the
setMessageDrivenContext() and the ejbCreate()
methods. At this point the message-driven bean is ready to receive messages.
When a bean instance is processing a JMS message, its
onMessage method is being invoked. When the EJB container removes a bean
instance, it first calls the ejbRemove method
before the instance is ready for garbage collection. The life cycle of a
message-driven bean is depicted in the following figure.
When defining a message-driven bean in WebLogic, in most cases you will
implement the onMessage method to execute a
particular business task, and use the ejbCreate
method to implement actions that only need to be executed once, such as looking
up the home interfaces of entity beans that are invoked by the message-driven
bean's onMessage method. A typical example of
simple message-driven bean is given below. Notice that the
ejbCreate method is used to find the home
interface of a Recording entity bean, while the
onMessage method processes the message and invokes the Recording bean:
/**
* @ejbgen:message-driven
* ejb-name = Statistics
* destination-jndi-name="jms.EJBTutorialSampleJmsQ"
* destination-type = javax.jms.Queue
*
* @ejbgen:ejb-local-ref
* type="Entity"
* name="ejb/recordLink"
* local="bands.Recording"
* link="Recording"
* home="bands.RecordingHome"
*/
public class StatisticsBean extends GenericMessageDrivenBean implements MessageDrivenBean, MessageListener
{
private RecordingHome recordingHome;
public void ejbCreate() {
try {
javax.naming.Context ic = new InitialContext();
recordingHome = (RecordingHome)ic.lookup("java:/comp/env/ejb/recordLink");
}
catch(NamingException ne) {
System.out.println("Encountered the following naming exception: " + ne.getMessage());
}
}
public void onMessage(Message msg) {
try {
// read the message
MapMessage recordingMsg = (MapMessage)msg;
String bandName = recordingMsg.getString("bandName");
String recordingTitle = recordingMsg.getString("recordingTitle");
...
// save the rating with the recording
Recording album = recordingHome.findByPrimaryKey(new RecordingBeanPK(bandName, recordingTitle));
album.setRating(rating);
}
catch(Exception e) {
System.out.println("Encountered the following exception: " + e.getMessage());
}
}
}
You can implement the ejbRemove method if
cleanup is required before the object is removed, and you can implement
setMessageDrivenContext method if you need
access to the javax.ejb.MessageDrivenContext
provided by the EJB container. The MessageDrivenContext
contains information about the container, in particular its transaction methods;
for more information, see your favorite J2EE documentation. A message-driven
bean defined in WebLogic by default extends
weblogic.ejb.GenericMessageDrivenBean, which provides empty definitions
for all these methods with the exception of the
onMessage method; the definition of your bean must therefore implement
the onMessage method.
Using a JMS Control
In WebLogic you can use a JMS control to send JMS messages to a topic or
queue. JMS controls can be used to send messages in page flows and web services;
they cannot be used to send JMS messages in EJBs. You can use JMS controls to
both send and receive messages. In this case you will only be using the JMS
control to send messages, as a message-driven bean will be used to process
received messages. For details on how to create JMS controls for topics and
queues, see
JMS Control.
The advantage of using a JMS control is that the details of the JMS messaging
API are by and large taken care of by the JMS control. For example, the
following code samples shows the definition of a JMS control to send messages to
a queue:
/**
* @jc:jms send-type="queue" send-jndi-name="jms.SamplesAppMDBQ"
* connection-factory-jndi-name="weblogic.jws.jms.QueueConnectionFactory"
*/
public interface sendToQueueControl extends JMSControl, com.bea.control.ControlExtension
{
/**
* this method will send a javax.jms.Message to send-jndi-name
*/
public void sendJMSMessage(Message msg);
static final long serialVersionUID = 1L;
}
As the definition shows, you must still define the name of the queue to which
to send the JMS message as well as the name of a
QueueConnectionFactory. However, to send a message using this JMS
control, you simply need to invoke the method sendJMSMessage and pass a message
as the parameter. The JMS control handles the creation of a factory instance, a
connection, session, and so forth.
When using a JMS control from a page flow or web service, you can use a
built-in control to encapsulate all the message sending related logic, instead
of defining this directly in the page flow or web service. For instance, the
following built-in control code snippet uses the JMS control defined above to
send 20 messages at one time to a queue.
...
public class MessageSenderImpl implements MessageSender, ControlSource
{
/**
* @common:control
*/
private messageDriven.sendToQueueControl queueSend;
/**
* @common:operation
* @common:message-buffer enable="true"
*/
public void add20ViaQueue(int currentNum) throws JMSException
{
String name;
for(int i = 0; i < 20; i++) {
MapMessage msg = queueSend.getSession().createMapMessage();
msg.setStringProperty("Command", "Add");
name = Integer.toString(currentNum + i);
msg.setString("tokenName", name);
queueSend.sendJMSMessage(msg);
}
}
}
For more information on built-in controls, see
Building Custom Java Controls. The use of JMS controls and built-in controls
in a page flow is demonstrated in the
Message-Driven Bean
Sample.
Sending
Messages from EJBs
To send JMS message from an EJB, you cannot use JMS controls and must use the
'standard' JMS messaging API. (You can also use the standard messaging API in
page flows and web services if you do not want to use a JMS control.) The
following code sample shows a session bean sending a message to a queue. The
queue and QueueConnectionFactory are defined
using
@ejbgen:resource-env-ref and
@ejbgen:resource-ref annotations. In the bean's
ejbCreate method the queue and
QueueConnectionFactory are located (this only need to be done once),
while the method executeTask contains the
correct procedure to create a connect, session, sender, and message before
sending the JMS message.
/**
* @ejbgen:resource-ref
* auth="Container"
* jndi-name = "weblogic.jws.jms.QueueConnectionFactory"
* name = "jms/QueueConnectionFactory"
* type="javax.jms.QueueConnectionFactory"
*
* @ejbgen:resource-env-ref
* jndi-name="jms.ASampleJmsQ"
* name="jms/ASampleJmsQ"
* type = "javax.jms.Queue"
*
* ...
*/
public class ASessionBean extends GenericSessionBean implements SessionBean
{
private QueueConnectionFactory factory;
private Queue queue;
public void ejbCreate() {
try {
javax.naming.Context ic = new InitialContext();
factory = (QueueConnectionFactory) ic.lookup("java:comp/env/jms/QueueConnectionFactory");
queue = (Queue) ic.lookup("java:comp/env/jms/ASampleJmsQ");
}
catch (NamingException ne) {
throw new EJBException(ne);
}
}
/**
* @ejbgen:local-method
*/
public void executeTask(String prop1, String prop2)
{
try {
//interpret the properties
...
//send a message
QueueConnection connect = factory.createQueueConnection();
QueueSession session = connect.createQueueSession(true,Session.AUTO_ACKNOWLEDGE);
QueueSender sender = session.createSender(queue);
MapMessage recordingMsg = session.createMapMessage();
recordingMsg.setString("Property1", prop1);
recordingMsg.setString("Property2", prop2);
sender.send(recordingMsg);
connect.close();
}
catch(JMSException ne) {
throw new EJBException(ne);
}
}
}
JMS Message Types
The JMS messaging API defines various message types, allowing for different
types of information to be included in the body of a JMS message. Frequently
used message types are TextMessage to send a text string,
MapMessage to create sets of name-value pairs, and ObjectMessage
to send a serializable object. For more details on the various message types,
see your favorite J2EE documentation or the
javax.jms.Message API documentation at
http://java.sun.com.
Message Selectors
A JMS message can also contain message properties. There are different types
of properties which can serve a number of functions. One common use of a message
property is to set a message selector. A message selector is a property that is
used by a message-driven bean to determine whether it should process this
message or should leave its processing up to another message-driven bean
listening to the same topic or queue.
The following example shows how to set a String property that will act as a
message selector for a JMS MapMessage:
Message msg = session.createMapMessage();
msg.setStringProperty("MyMessage", "Build 126"");
A message-driven bean that is defined to specifically process this type of
message will include a message selector property:
* @ejbgen:message-driven
* message-selector="MyMessage = 'Build126'"
* ejb-name = MDBean
* ...
*/
public class MDBean extends GenericMessageDrivenBean implements MessageDrivenBean, MessageListener
{
...
In other words, this bean will respond specifically to this type of message
and will leave the processing of other messages up to other beans listening to
this topic or queue. By using a message selector you can send different types of
messages to the same channel instead of having to create a separate queue/topic
for every type of message. For more details on the various message properties,
see your favorite J2EE documentation or the
javax.jms.Message API documentation at
http://java.sun.com.
When a JMS message is sent to a topic or queue, a message-driven bean
instance will interpret and process the message, as specified in its
onMessage method. When a JMS message is sent
to a topic, a publish-and-subscribe system, an instance of every
message-driven bean class listening to this topic will in principle receive
and process this message. However, if the message contains a message
selector, only the message-driven bean(s) matching the message selector will
process the message. When a JMS message is sent to a queue, a point-to-point
system, only one message-driven bean will process the message, even when
multiple bean classes are listening to the queue. Again, the use of a
message selector might limit the bean processing the message. For more on
message selectors, . How the JMS message is processed fully depends
on the business task that is being modeled. It might range from simply
logging the message to executing a range of different tasks which include
invoking methods on session and entity beans, or
sending JMS messages to be processed by other message-driven beans. The
following code sample shows one use of a message-driven bean. This bean
responds only to JMS messages delivered via the
jms/SamplesAppMDBQ queue and contain the message selector
Command= 'Delete'. When processing a JMS
message, an instance of this bean invokes the query method
findAll of the entity bean SimpleToken,
and subsequently deletes all SimpleToken records from the underlying
database.
/**
* @ejbgen:message-driven
* message-selector="Command = 'Delete'"
* ejb-name = DeleteViaQMD
* destination-jndi-name = jms/SamplesAppMDBQ
* destination-type = javax.jms.Queue
*
* @ejbgen:ejb-local-ref link="SimpleToken"
*/
public class DeleteViaQMDBean
extends GenericMessageDrivenBean
implements MessageDrivenBean, MessageListener
{
private SimpleTokenHome tokenHome;
public void ejbCreate() {
try {
javax.naming.Context ic = new InitialContext();
tokenHome = (SimpleTokenHome)ic.lookup("java:/comp/env/ejb/SimpleToken");
}
catch(NamingException ne) {
System.out.println("Encountered the following naming exception: " + ne.getMessage());
}
}
public void onMessage(Message msg) {
try {
Iterator allIter = tokenHome.findAll().iterator();
while(allIter.hasNext()) {
((SimpleToken) allIter.next()).remove();
}
}
catch(Exception e) {
System.out.println("Encountered the following exception: " + e.getMessage());
}
}
}
Acknowledgement and Transactions
When a message-driven bean instance receives a message, and it is not
part of a container-managed transaction, by default it immediately sends an
acknowledgement to the JMS provider notifying that the message has been
received. This will prevent the JMS provider from attempting to redeliver
it. However, the acknowledgement only indicates that a message has been
successfully received, but it does not guarantee that the message is
successfully processed. For instance, a system exception might occur when
attempting to locate an entity bean or update its records, causing the
processing to fail.
If you want to ensure that a message is redelivered when processing
fails, you can make the message-driven bean be part of a transaction. The
easiest approach is to use container-managed transaction, where the EJB
container is responsible for transaction management. To enable
container-managed transaction for a message-driven bean, make sure that your
cursor is placed inside the ejbgen:message-driven
tag and use the Property Editor to set the
transaction-type to Container and the
default-transaction to Required. When JMS message
processing executes successfully, any changes made during this business
task, such as update to entity bean records, are committed and
acknowledgement is sent to the JMS provider. However, when JMS message
processing fails due to a system exception, any changes are rolled back and
receipt is not acknowledged. In other words, after processing fails, the JMS
provider will attempt to resend the JMS message until processing is
successfully or until the maximum number of redelivery attempts specified
for this topic or queue has been reached.
Note. When a message-driven bean is
part of a transaction, it executes as part of its own transaction. In other
words, if the transaction fails, changes that were made as part of the
onMessage method are rolled back, but the
occurrence of an exception has no direct effect on the EJB or client
application sending the JMS message, as the sender and the message-driven
bean are decoupled.
To learn more about container-managed transactions, see
EJBs
and Transactions. To learn more about bean-managed transaction (that is,
explicit transaction management), please refer to your favorite J2EE
documentation.
|