In This Chapter
As organizations strive to develop collaborative software solutions, it is not uncommon to discover that an enterprise software system does not actually exist or function as a single software entity. Instead, there is a high degree of probability that the enterprise system actually comprises numerous independent software subsystems, each of which can exist on separate software platforms with unique operational boundaries.
The communication mechanism and the development plumbing effort involved to enable applications to communicate between themselves really depend on the interoperability of their respective implementation technologies. For example, the Remote Procedure Call (RPC) model is a viable communication solution between applications that have accessible and well-defined interfaces for processing client requests. The downside of the RPC model is that, regardless of whether communication between applications is enabled through an application adapter, you are implementing tight integration at the interface level (object class/methods) between possibly autonomous applications. Also, communication could be limited to synchronous interactions; for example, Enterprise JavaBean (EJB) clients use the home and remote interfaces to make synchronous RMI calls to their target EJB server. The client invoking the EJB call pauses its processing (blocks) until the EJB server returns processing control to the client through a response.
When loose coupling, reliability, and asynchronous processing are requirements between J2EE applications/components and Enterprise Information Systems (EISs), leveraging an intermediary messaging system that is a Java Messaging Service (JMS) provider and also interoperable with the EIS is probably your best option for centralizing communication flow through the production and consumption of messages.
A JMS provider implements the JMS API, a set of standard interfaces that define how a JMS client accesses the facilities of a messaging service. This chapter uses a lot of concepts related to messaging domains and the JMS API. If you’re not familiar with either topic, reading Chapter 15, “The Java Messaging Service (JMS),” is recommended before proceeding with this chapter.
By leveraging a messaging model,
The calling application (message producer) sends a message to a JMS destination (queue or topic) implemented by the JMS provider, such as WebLogic JMS Server, which can deliver messages to the recipient (message consumer) immediately or store them until the consumer is available. In the meantime, the message producer does not block processing by waiting for a response from the message consumer. This is asynchronous processing.
The message producer does not need to know the implementation details of the message consumer—this is called loose coupling.
The JMS provider can persist messages if required—this is referred to as reliability.
A shortfall of the EJB 1.1 specification is that it did not provide an EJB type for handling asynchronous communications. However, a Java client could be used as a synchronous or asynchronous message listener for a JMS destination, which made it possible to route messages to an EJB for processing.
One of the goals of the EJB 2.0 specification is to provide a tighter integration with the JMS API, which was successfully achieved by introducing the message-driven bean (MDB), a new EJB type that can consume asynchronous JMS messages by listening to a queue or subscribing to a topic. By fulfilling the consumer side of the JMS messaging model, MDBs are asynchronous message listeners/consumers that are associated with only one queue or topic.
Figure 22.1 illustrates the partition of component functionality provided by J2EE and the EJB 2.0 specification.
As an EJB type, MDBs reside within the context of an EJB container and, similar to standard JMS message listeners, cannot interact directly with their JMS clients because they do not have a home or remote interface. For this reason, MDBs are considered anonymous because they have no client-visible identity.
Similar to stateless session beans, MDB instances also have no conversational state, which implies that all bean instances are equivalent when they are not involved in servicing a client message. This characteristic permits the EJB container to pool and create multiple instances of the same MDB based on the throughput of messages in the MDB’s JMS destination, hence enabling the concurrent consumption of messages, as shown in Figure 22.2.
A client accesses an MDB through JMS by sending messages to a JMS destination (queue or topic) for which the MDB class is assigned as a message listener. MDBs are associated with a specific JMS destination via their deployment descriptors.
Standard JMS message listeners receive messages asynchronously by implementing javax.jms.MessageListener
with the onMessage()
method.
When a message arrives in a JMS destination targeted for an MDB, the JMS provider and the EJB container automatically collaborate to deliver the message to the MDB. One instance from the MDB bean pool is selected to handle the message. Similar to a standard JMS message listener, an MDB contains an onMessage()
method that is called automatically when a message arrives. However, different messages can be processed concurrently by different MDB instances, which provides performance and scalability benefits.
After an MDB consumes a message, it can process the message or pass the message to another EJB type for processing, as shown in Figure 22.3.
The primary benefit of using MDBs for asynchronous consumption of messages is derived from MDBs existing completely in the context of an EJB container and, therefore, being able to leverage EJB services the container provides. For example, the WebLogic EJB container provides a standard set of services, such as concurrency control, persistence, security, transaction management, locking, clustering, and complete JMS infrastructure support, which would otherwise need to be configured in code and via the Administration Console for a standard JMS Listener.
MDBs can initiate Container-Managed and Bean-Managed Transactions, which are discussed in “An Overview of Message-Driven Bean Transactions” later in this chapter.
Although an MDB can serve only one JMS destination, its stateless nature allows the WebLogic EJB container to create multiple MDB instances as necessary to process large volumes of messages concurrently, hence providing a scalable mechanism for concurrent consumption/processing of messages. In contrast, you would have to configure the standard JMS Listener class to be a member of a serverwide session pool via the Administration Console.
Instance pooling within the EJB container implies that messages are not received or processed in sequential order, although individual onMessage()
calls to an MDB instance are serialized.
As you will learn later in the “The Life Cycle of a Message-Driven Bean” section, WebLogic Server maintains a free pool of EJBs for every MDB class to expedite processing asynchronous messages.
Another benefit of using MDBs is that they inherit the write-once, deploy-anywhere quality of EJBs, as the associated JMS destination and server resource attributes are assigned only at deployment time. This provides better portability of MDBs between different implementations of the EJB container.
MDBs are suitable for use over standard JMS Consumers in the following situations:
Broadcasting a variable volume of messages to a large subscription base—. Because the EJB server can instantiate MDBs to handle a varied volume of messages from a JMS destination, MDBs are well suited to applications, such as a newsgroup application, that generate and broadcast a large volume of messages to a subscription base.
Legacy application integration into J2EE-based enterprise applications—. MDBs can be used to process application requests that rely on message transfers, messaging routing, or message filtering operations.
As a JMS Listener—. MDBs can be used as message listeners that receive messages from a JMS destination and then route them to other bean types for processing.
Enabling JMS transaction services—. MDBs can demarcate transaction boundaries by using Bean-Managed or Container-Managed Transactions.
One constraint of using MDBs compared to standard JMS Listeners is that you can associate an MDB only to a single JMS destination (queue or topic). If you require a single JMS Consumer to process messages from multiple JMS destinations, you can use a standard JMS Consumer or deploy MDB classes for each JMS destination.
Even though an MDB is a bean type that can mimic the behavior of stateless session bean instances at times (pooling) and, like other EJBs, live within the boundaries of an EJB container, the following differences between MDBs and the other bean types (session and entity) further clarify their unique message consumption characteristics:
MDBs have no home or remote interface and, therefore, internal or external clients cannot directly access them.
The EJB container maintains an MDB’s entire life cycle, so MDB instances cannot be created or removed as a result of client requests or other API calls.
MDBs process multiple JMS messages asynchronously, instead of processing a serialized sequence of method calls, as with session and entity beans.
MDBs do not propagate application or remote exceptions back to their JMS clients, which forces exceptions to be handled within a bean’s onMessage()
method.
Because an MDB’s role is to consume JMS messages, it has only one input parameter that is a subclass of javax.jms.Message
, which is used to invoke the bean.
Table 22.1 summarizes the differences between message-driven, session, and entity beans.
Table 22.1. Summary of EJB Type Characteristics
Functionality | Message-Driven Bean | Session Bean | Entity Bean |
---|---|---|---|
Can implement a remote/local interface | No | Yes | Yes |
Can implement a home interface | No | Yes | Yes |
Exception propagation to client | No | Yes | Yes |
Receives messages via a message listener—the | Yes | No | No |
Can post a message to a queue or topic | Yes | Yes | Yes |
Can return values to the caller | No | Yes | Yes |
Can take advantage of pooling | Yes | Yes (stateless session beans) | Yes |
Is isolated from the client | Yes | No | No |
Life cycle is managed by the EJB container | Yes | Yes | Yes |
Yes | No | No |
An MDB class implements the javax.ejb.MessageDrivenBean
and javax.jms.MessageListener
interfaces, as shown in Figure 22.4.
The MessageDrivenBean
interface, which extends the javax.ejb.EnterpriseBean
interface, declares two methods: ejbRemove()
and setMessageDrivenContext()
. The bean developer must implement these methods along with the ejbCreate()
method inherited from the EnterpriseBean
interface. The MessageListener
interface declares only one method that the bean developer must implement: onMessage()
. Listing 22.1 shows the basic components required to implement an MDB.
Example 22.1. The Basic Components of an MDB Class
public class myMDB implements MessageDrivenBean, MessageListener{ public myMDB() {...}; // Required Bean Constructor public void ejbCreate() {...} public void setMessageDrivenContext(MessageDrivenContext ...) {...} public void onMessage(javax.jms.Message MessageName) {...} public void ejbRemove() {...} }
The following sections briefly describe the four methods that constitute an MDB class and discuss how to handle exceptions in MDBs.
The MDB class is allowed to implement other methods, such as utility methods called internally by onMessage()
. However, these methods are never called directly by the container or any client.
The container calls the ejbCreate()
method for bean instantiation into the WebLogic EJB container’s free pool, which is an area of memory WebLogic Server reserves to maintain a set of method-ready stateless bean instances (session and MDBs). This method is a convenient place to implement logic for allocating the necessary resources to process messages. For example, if an MDB sends messages to other destinations, you can place code in the ejbCreate()
method to look up JMS API connection factories and destinations and create the JMS API connection.
The MDB class must define one ejbCreate()
method, and its signature must abide by the following rules:
The setMessageDrivenContext()
method is called before ejbCreate()
and invoked by the EJB container to associate an MDB instance with its context, which is maintained by the container. The EJB container passes the javax.ejb. MessageDrivenContext
object into this method, which is usually established as a member variable, thus allowing the MDB instance to access its context information in subsequent methods.
For example, the transactional context that the EJB container sets for an MDB can be accessed via the following methods from the MessageDrivenContext
interface:
setRollbackOnly()
—. Marks a transaction to roll back so that it cannot commit. The bean can use this method only if it uses Container-Managed Transaction demarcation.
getRollbackOnly()
—. Returns a Boolean value indicating whether the transaction can commit. The bean can use this method only if it uses Container-Managed Transaction demarcation.
getUserTransaction()
—. Returns a reference to the javax.transaction.UserTransaction
object that allows the bean to initiate, commit, and roll back transactions from within the bean. The bean can use this method only if it uses Bean-Managed Transaction demarcation.
Even though the getCallerPrincipal()
, isCallerInRole()
, getEJBHome()
, and getEJBLocalHome()
methods are inherited as part of the MessageDrivenContext
interface, they should not be called by MDBs for these reasons:
The bean does not receive a client security context.
The bean does not have EJBHome
or EJBLocalHome
objects associated with it.
The MDB class must define one setMessageDrivenContext()
method. This method’s signature must abide by the following rules:
The EJB container calls the onMessage()
method when it determines that an MDB instance should receive a message from a JMS queue or topic. The onMessage()
method accepts an object of type javax.jms.Message object
as an input argument. Within this method, the bean provider implements the business logic to process the received message, which is cast to one of the five JMS message types. After a message has been processed, the bean instance is available to receive other requests.
The MDB class must define one onMessage()
method; its signature must abide by the following rules:
The EJB container calls this method to remove the bean from WebLogic Server’s free pool. You can also use this method to implement the code for releasing any resources allocated in the bean’s ejbCreate()
or setMessageDrivenContext()
methods, such as JMS or JDBC connections.
The MDB class must define one ejbRemove()
method. This method’s signature must abide by the following rules:
MDBs do not propagate exceptions to the client, and for this reason they should not throw an application exception or a RemoteException
. If any of the bean’s methods throws such an exception, WebLogic Server immediately removes the EJB instance without calling ejbRemove()
. From a client’s perspective, the MDB still exists, as future messages are forwarded to a new bean instance that WebLogic Server allocates from the free pool. However, from a WebLogic Server perspective, these types of exceptions are undesirable because any resources the MDB allocated would not be released appropriately.
To counter these exceptions, the bean provider must handle all exceptions within the MDB’s methods, for example:
For the onMessage()
method, use a try-catch
block to catch any exceptions.
For the ejbCreate()
method, you can include javax.ejb.CreateException
in the throws
clause.
For the ejbRemove()
method, you can use javax.ejb.EJBExecption
in the throws
clause.
In addition to the preceding exception-handling techniques, a bean developer should implement additional internal methods to perform resource cleanup routines outside the ejbRemove()
method—for example, to clean up resources allocated through the ejbCreate()
and setMessageDrivenContext()
methods.
Because an MDB exists in the context of the WebLogic EJB container, its lifetime is also controlled by the container. An MDB initially exists in the Does Not Exist state in the WebLogic EJB container. At its instance creation, the bean is placed in a Method Ready state in WebLogic’s EJB free pool waiting to process a message on the JMS destination (queue or topic) it’s assigned to as a message listener. As shown in Figure 22.5, the following steps describe the life cycle of an MDB instance:
An MDB instance’s life starts when the container calls newInstance()
on the MDB class to create a new instance.
The container provides the EJB container context reference to the MDB by calling the setMessageDrivenContext()
method.
The container then calls ejbCreate()
on the MDB instance to instantiate the bean to the EJB free pool (the Method Ready state). The MDB instance is then ready to receive a message sent via its assigned JMS destination by any client.
When a JMS message is posted to a topic or queue, the container calls the designated MDB’s onMessage()
method. The bean instance in turn parses and processes the message. After the method completes, the bean instance is returned to the free pool.
The MDB instance goes into the Does Not Exist state from the Method Ready state, when the container decides to reduce the number of bean instances in the free pool, either to reclaim the allocated resources or because it no longer needs additional instances of the bean in the free pool. The WebLogic EJB container removes the bean from the free pool by calling the bean’s ejbRemove()
method.
When WebLogic Server starts, if MDBs have been deployed to the server, the WebLogic EJB container automatically instantiates multiple instances of the bean, as specified by the value of the <initial-beans-in-free-pool>
element in the weblogic-ejb-jar.xml
deployment descriptor. These bean instances are placed in the free pool, ready to service an incoming message on their assigned JMS destinations. Populating the free pool in this manner improves the initial response time for the MDB because initial requests for the bean can be satisfied without generating a new bean instance.
<initial-beans-in-free-pool>
defaults to 0 if the element is not defined, which implies that WebLogic Server will not prepopulate the free pool with any MDB instances.
The EJB container creates new instances of MDBs as needed for concurrent and parallel message processing. However, the maximum number of MDB instances that can be accommodated in the free pool depends on two factors:
The value of the <max-beans-in-free-pool>
element in the weblogic-ejb-jar.xml
deployment descriptor, which places an upper boundary on the number of MDB instances the container creates.
The number of execution threads available in WebLogic Server to service each message consumption by an MDB. For example, if <max-beans-in-free-pool>
is set to 20 but only 10 execution threads are available to the EJB container, only 10 of the MDBs actually receive messages.
▸ To learn how to configure WebLogic Server execute threads, see “The WebLogic Server Execute Queues,” p. 1021, in Chapter 28, “Performance Tuning the WebLogic Server.”
The default value of the <max-beans-in-free-pool>
element enables you to run beans in parallel, using as many threads as possible. The only reason to change this setting is to limit the number of beans running in parallel because of memory/processor constraints that affect the performance or operation of WebLogic Server.
Hence, when a JMS queue or topic receives a message, the WebLogic EJB container calls an associated MDB as follows:
If a bean instance is available in the free pool, WebLogic Server uses that instance, and the message is consumed through the bean’s onMessage()
method.
If no bean instances are available in the free pool because all instances of an MDB class are active and <max-beans-in-free-pool>
has been reached, the message waits in the JMS destination until an MDB becomes available to consume the message.
If the free pool is empty and <max-beans-in-free-pool>
has not been reached, the EJB container creates a new instance of the MDB to consume the message.
The series of steps in the following sections guide you through implementing and deploying a simple MDB (myMessageDrivenBean
) that consumes a text message from a JMS topic and prints the message to your WebLogic Server’s standard out (stdout
). An overview of this MDB example is shown in Figure 22.6.
Because MDBs have only four methods where you can implement code, they are quite easy to develop. The code for myMessageDrivenBean
is shown in Listing 22.2.
Example 22.2. The myMessageDrivenBean
MDB Code
package MDBExample; import javax.ejb.EJBException; import javax.ejb.CreateException; import javax.ejb.MessageDrivenBean; import javax.ejb.MessageDrivenContext; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class myMessageDrivenBean implements MessageDrivenBean, MessageListener { private MessageDrivenContext mdbContext; // A simple logging mechanism private void log(String s) { if (true) System.out.println(s); } // container callback methods public void ejbRemove() throws EJBException { log("ejbRemove called"); } public void setMessageDrivenContext(MessageDrivenContext ctx) { mdbContext = ctx; log("context set"); } public void ejbCreate () throws CreateException { log("ejbCreate called"); } // The implementation of the onMessage method public void onMessage(Message msg) { TextMessage tm = (TextMessage) msg; try { log("onMessage called"); String text = tm.getText(); System.out.println("[myMessageDrivenBean] Received message: " + text ); } catch(JMSException ex) { System.out.println("Caught JMSException: " + ex ); } } }
To show you when the WebLogic EJB container calls an MDB’s method, simple logging to WebLogic Server’s stdout
has been provided for each method in Listing 22.2.
After you have created this code using a Java IDE or text editor, follow these steps:
Create a temporary staging directory anywhere on your hard drive. This example uses d:stage
as the staging directory. For this example, it’s assumed that the staging directory is located on a file system local to WebLogic Server.
Save the file as myMessageDrivenBean.java
in your staging directory.
Create a META-INF
subdirectory in your staging directory.
Open a command shell session and set your Java environment, using the WebLogic setEnv
batch file in your WebLogic domain directory.
From within your staging directory, compile your .java
file to a .class
file (myMessageDrivenBean.class
) using the Java javac
utility, as follows:
javac -d c:stage myMessageDrivenBean.java
The bytecode myMessageDrivenBean.class
file is then created in the d:stageMDBExample
directory.
For this example, you need to create the following deployment descriptors with a text or XML editor and place them in the META-INF
subdirectory within your staging directory.
This deployment descriptor describes an EJB’s structure and declares its internal dependences. You can also include the bean’s application assembly information, which describes how the bean in the ejb-jar.xml
file is assembled into an application deployment unit. The complete ejb-jar.xml
deployment descriptor for myMessageDrivenBean
is shown in Listing 22.3.
Example 22.3. The ejb-jar.xml
Deployment Descriptor for myMessageDrivenBean
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <!-- Generated XML! --> <ejb-jar> <enterprise-beans> <message-driven> <ejb-name>myMDB</ejb-name> <ejb-class>MDBExample.myMessageDrivenBean</ejb-class> <transaction-type>Container</transaction-type> <acknowledge-mode>Auto-acknowledge</acknowledge-mode> <message-driven-destination> <destination-type>javax.jms.Topic</destination-type> <subscription-durability>NonDurable</subscription-durability> </message-driven-destination> </message-driven> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>myMDB</ejb-name> <method-name>onMessage</method-name> </method> <trans-attribute>NotSupported</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
Table 22.2 explains the deployment descriptor elements that pertain to MDBs in the ejb-jar.xml
file.
Table 22.2. A Description of Elements in the ejb-jar.xml
File
Optionally, you can also use the <message-selector>
element to specify a valid JMS message selector query used to restrict the message type an MDB consumes, as shown here:
<message-selector>company_name='objectmind'</message-selector>
This deployment descriptor contains deployment information for a bean that is WebLogic specific, such as the initial and maximum number of MDBs in the free pool and mappings to WebLogic resources. The complete weblogic-ejb-jar.xml
deployment descriptor for myMessageDrivenBean
is shown in Listing 22.4.
Example 22.4. The weblogic-ejb-jar.xml
Deployment Descriptor for myMessageDrivenBean
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN' 'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'> <!-- Generated XML! --> <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>myMDB</ejb-name> <message-driven-descriptor> <pool> <max-beans-in-free-pool>15</max-beans-in-free-pool> <initial-beans-in-free-pool>5</initial-beans-in-free-pool> </pool> <destination-jndi-name>myJMSTopic</destination-jndi-name> <jms-polling-interval-seconds>10</jms-polling-interval-seconds> </message-driven-descriptor> <transaction-descriptor> </transaction-descriptor> <jndi-name>myMDB</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
Table 22.3 explains the deployment descriptor elements that pertain to MDBs in the weblogic-ejb-jar.xml
file.
Table 22.3. A Description of Elements in the weblogic-ejb-jar.xml
File
Element | Description |
---|---|
Specifies the name of the bean assigned in the | |
Specifies the upper boundary for the number of MDB instances that can be placed in the EJB container’s free pool. | |
Specifies how many instances of the bean should be created by the EJB container and placed in the free pool when the bean is deployed. | |
Associates an MDB with a JMS queue or topic that has already been deployed in the WebLogic JNDI tree. | |
Specifies the number of seconds between each attempt to reconnect to the JMS destination. | |
By default, WebLogic Server uses the standard JMS connection factory (weblogic.jms.MessageDrivenBeanConnectionFactory
) to enable an MDB to gain connections to its JMS destination. However, if you have created your own JMS connection factory, you need to specify its JNDI name via the <connection-factory-jndi-name>
element in the weblogic-ejb-jar.xml
file:
<message-driven-descriptor> <connection-factory-jndi-name>myJMSConnectionFactory</connection-factory-jndi-name> </message-driven-descriptor>
After the MDB class and its associated deployment descriptors are in their respective locations in the staging directory, the next step is packaging these files into a deployment unit—the EJB JAR file. To do this, issue the following jar
command from within your staging directory after ensuring that you have set up your Java environment:
jar cvf std_mdb.jar MDBExample META_INF
Issuing this command creates the std_mdb.jar
file (the deployment unit).
To generate container classes for this deployment unit, which the WebLogic EJB container uses to access the MDB, you need to use the WebLogic ejbc
utility.
The ejbc
compiler generates container classes according to the deployment elements you have specified in the weblogic-ejb.jar.xml
deployment descriptor file. This is the ejbc
command for generating container classes for the std_mdb.jar
deployment unit:
java weblogic.ejbc std_mdb.jar mdb.jar
The ejbc
utility accepts both JAR files and exploded directories as input. If you specify an output JAR file, ejbc
places all generated files in the JAR file.
This command creates a new JAR file (mdb.jar
) with the original EJB classes, interfaces, and XML deployment files in the JAR as well as the new container classes that ejbc
generates. You will deploy mdb.jar
to WebLogic Server later in this chapter.
If you use the ejbc
utility to output to a JAR file, the output JAR name must be different from the input JAR name.
Figure 22.7 shows issuing the jar
and ejbc
commands through a batch file named build.cmd
.
Before you can deploy your MDB to WebLogic Server, you must ensure that the JMS destination associated with the bean has been configured in the target WebLogic Server and, most important, that the topic is active. The JNDI name for the JMS topic associated with this example is myJMSTopic
, which is specified via the <destination-jndi-name> weblogic-ejb-jar.xml
element.
▸ To learn how to configure a JMS destination in WebLogic Server, see “Creating a JMS Destination—Queue or Topic,” p. 495, in Chapter 15, “The Java Messaging Service (JMS).”
To deploy the mdb.jar
deployment unit to your target WebLogic Server, follow these steps:
Launch and log in to the Administration Console.
Expand the Deployments node in the left pane.
Right-click the EJB node and select the Configure a New EJB option.
Locate the mdb.jar
archive in your file system using the Administration Console’s directory navigation mechanism.
Click [select]
to the left of the mdb.jar
file.
Enter a name for the MDB, such as myMessageDrivenBean
, to identify it in the Administration Console.
Click Configure and Deploy. The Administration Console displays the Deploy tab, which lists deployment status and activities for the MDB. A successful deployment of the MDB example is shown in Figure 22.8.
After your MDB has been deployed to WebLogic Server, the EJB container creates five instances of the bean in the free pool, as specified by the <initial-beans-in-free-pool>
element in weblogic-ejb-jar.xml
. Because logging was enabled in each method of myMessageDrivenBean
(refer to Listing 22.2), the methods called to instantiate the been in the free pool are displayed in WebLogic Server’s stdout
, as shown in Figure 22.9.
After the MDB has been successfully deployed, using these steps to monitor the bean from the Administration Console is a good idea:
In the Deploy tab, shown in Figure 22.10, you can easily verify how many bean instances have been created and placed in the free pool and ensure that the bean is listening on an active JMS destination.
To test your MDB, you need to write a JMS client that sends a text message to the bean’s associated JMS topic (myJMSTopic
). After the message is sent to the JMS topic, the EJB container calls the bean’s onMessage()
method for message consumption and processing. Listing 22.5 provides the code for a JMS client (PubSubProducer
) that sends five text messages to myJMSTopic
.
In Listing 22.5, you have to modify the context.PROVIDER_URL
value accordingly to match your WebLogic Server host and port number.
Example 22.5. The PubSubProducer
JMS Client
import javax.naming.*; import javax.jms.*; import java.util.*; public class PubSubProducer { private final static String NAMING_FACTORY= "weblogic.jndi.WLInitialContextFactory"; private final static String TCON_FACTORY="javax.jms.TopicConnectionFactory"; private final static String MESSAGE_TOPIC="myJMSTopic"; public static void main ( String[] argv ){ try { // Create a JNDI initial context Properties props = new Properties() ; props.put( Context.INITIAL_CONTEXT_FACTORY, NAMING_FACTORY); props.put( Context.PROVIDER_URL, "t3://einstein:7001"); InitialContext ic = new InitialContext(props); // Look up a connection factory in JNDI TopicConnectionFactory tCF = (TopicConnectionFactory)ic.lookup(TCON_FACTORY); // Create a connection TopicConnection tCon = tCF.createTopicConnection(); // Create a session TopicSession tSession = tCon.createTopicSession(false,Session.AUTO_ACKNOWLEDGE); // Look up the topic Topic topic = (Topic)ic.lookup(MESSAGE_TOPIC); // Create a TopicPublisher bound to the topic TopicPublisher tPublisher = tSession.createPublisher(topic); // Create a TextMessage TextMessage mes = tSession.createTextMessage("My First Message-Driven Bean Message!"); // Start the topic, send the message, and close the topic tCon.start() ; for(int i = 0; i<5; i++ ) { tPublisher.publish(mes); System.out.println(" Sending Message - " + (i+1) + " to the JMS Topic"); } tCon.close() ; } catch ( NamingException e1 ) { System.out.println(e1); } catch ( JMSException e2 ) { System.out.println(e2); } } }
To test your MDB, first compile the JMS client code (PubSubProducer.java
) using the javac
utility from a command shell. Then run the JMS client from the command shell by using the following command:
Java PubSubProducer
As shown in Figure 22.11, the JMS client sends five messages to the JMS topic, and the onMessage()
method is called five times to process each message.
A transaction in JMS cannot include both producing and consuming messages as one logical unit of work, so both processes need to be treated within their own transactional contexts. The reason for this transactional separation is that a JMS client’s (JMS Producer) transaction context does not flow with a JMS message, which implies that transactions initiated from a JMS client cannot be propagated to the message consumer.
If a transaction is started from a JMS Producer, the JMS message is not immediately propagated to a JMS destination (a queue or topic). Instead, the message is buffered in memory or stored in the JMS server’s persistent storage device (a JDBC database or file). Only if the transaction commits will messages in the buffer be sent to the target JMS destination as part of transaction commit. However, if the JMS producer’s transaction is rolled back, the JMS message is flushed from the buffer, and the message is not sent to the target JMS destination.
From a JMS Consumer’s perspective, such as an MDB, if a transaction commits, the processed JMS message is removed from the JMS destination. However, if the transaction rolls back, the JMS message stays in the JMS destination and is redelivered as follows:
When designing an MDB to use transactions, you must decide whether the bean will demarcate transactions programmatically via the onMessage()
method (Bean-Managed Transaction, or BMT, demarcation) or whether the EJB container will perform transaction demarcation (Container-Managed Transaction, or CMT, demarcation).
You specify the transaction demarcation type of an MDB via the <transaction-type>
element in the ejb-jar.xml
deployment descriptor, for example:
<transaction-type>Bean</transaction-type>
—. BMT demarcation
<transaction-type>Container</transaction-type>
—. CMT demarcation
With CMT demarcation, the EJB container demarcates transactions based on instructions in the ejb-jar.xml
deployment descriptor. These instructions, called transaction attributes (as shown in Listing 22.6), tell the container whether it should run an MDB’s onMessage()
method in a new transaction started by the container or run the method without any transactions.
Example 22.6. A Segment of ejb-jar.xml
Showing Transaction Attributes for the onMessage()
Method Using CMT
<ejb-jar>
...
<assembly-descriptor>
...
<container-transaction>
<description>Transactions required for this method.</description>
<method>
<ejb-name>myMDB</ejb-name>
<method-name>onMessage</method-name>
</method>
<trans-attribute>NotSupported or Required</trans-attribute>
</container-transaction>
...
</assembly-descriptor>
...
</ejb-jar>
As shown in Listing 22.6, these are the only transaction attributes that are applicable to MDBs with CMT demarcations:
NotSupported
—. The container calls the onMessage()
method with an unspecified transaction context, which implies the method cannot be used to initiate transactions.
Required
—. The EJB container automatically starts a new transaction before dequeuing the JMS message and before calling the onMessage()
method. Therefore, the EJB container calls onMessage()
with a valid transaction context and a message receipt from the JMS destination. The container attempts to commit the transaction after the onMessage()
method has completed. For MDBs that use CMT, the container automatically acknowledges a message receipt to the WebLogic JMS Server after the transaction commits.
If the onMessage()
method does not successfully complete or the container rolls back the transaction, the container does not acknowledge a message receipt; the message remains in its associated JMS destination and is delivered again to the MDB. When a message continuously gets redelivered to a container-managed MDB only to be rolled back, the message is referred to as a poison message. Poison messages can have a negative impact on WebLogic JMS Server’s performance. They also consume MDB resources from the free pool, so they should removed from the JMS destination when they’re detected. WebLogic JMS Server has built-in support for detecting message redelivery attempts and for redirecting a message to an error destination when redelivery attempts for the message reach a specific limit. You can configure WebLogic JMS Server to manage poison messages via the Administration Console, as shown in Figure 22.12.
When an MDB uses BMT demarcation, you must use the EJBContext.getUserTransaction()
callback to obtain a reference to the javax.transaction.UserTransaction
object. After you have an instance of javax.transaction.UserTransaction
, you can mark the beginning of a transaction with the begin()
method, and end the transaction with the commit()
or rollback()
method. If the transaction commits, a message acknowledgement is sent to WebLogic JMS Server based on the value the <acknowledge-mode>
element specifies in the ejb-jar.xml
deployment descriptor: Auto-acknowledge
or Dups-ok-acknowledge
. The skeleton code for an onMessage()
method using BMT is shown in Listing 22.7.
Example 22.7. Skeleton Code for the onMessage()
Method Showing How to Use BMT
public void onMessage(Message msg) { try { // get the UserTranaction from the EJB context javax.transaction.UserTransaction mdbtx = mdb_context.getUserTransaction(); // start the transaction mdbtx.begin(); do some processing... if (OK Condition) { // commit the transaction mdbtx.commit(); } else { // roll back transaction mdbx.rollback(); } } }
This chapter is in essence a continuation of Chapter 15, which introduces the concepts behind the JMS API and explains how you can communicate between applications and systems through the production and consumption of messages. However, in Chapter 15, only synchronous messaging was discussed.
This chapter has methodically introduced you to MDBs, J2EE’s solution to consuming JMS messages in an asynchronous manner, and their value as an EJB-type JMS message listener within the context of an EJB container. MDBs are easy to implement, and a simple example has been supplied so that you can get a handle on how to develop and deploy a working MDB to WebLogic Server.