Thursday, October 1, 2009

How to Use Message-Driven Bean in the Web Application

A sample that uses EJB 3.0 message-driven bean is described here. It is from the ejb3 example in weblogic 10.

Create MDB and Session Bean

To use MDB, two resources are needed: (1) Message Connection Factory (2) Message Destination( a queue or a topic ). These are configured in the admin server. They will have a JNDI name.

In this example, a message queue is created in Admin with the JNDI name "weblogic.examples.ejb30.ExampleQueue". A queue connection factory is created in Admin with the JNDI name "weblogic.examples.ejb30.QueueConnectionFactory".

For the MDB to work, there needs to be a sender to send the message. And the MDB will receive the message to process it.

The following is a sample MDB:
@MessageDriven(mappedName = "weblogic.examples.ejb30.ExampleQueue", name = "WatchProcessMDB", activationConfig = {@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")})
  public class WatchProcessMDB implements MessageListener
  {
    @PersistenceContext(unitName = "reviewSession")
    private EntityManager em;

    @TransactionAttribute(value = javax.ejb.TransactionAttributeType.REQUIRED)
    public void onMessage(Message msg)
    {
      // do the business logic using the msg and the EntityManager em
    }
  }
The following is the sample sender, which is a stateless session bean:
/**
   * A Stateless session bean get invoked when a review is added. It will push
   * the review into a jms destination. The review added event will be handled
   * asynchronously.
   */
  @Stateless
  @Remote(ReviewListener.class)
  public class ReviewListenerBean implements ReviewListener
  {
    // The JNDI name of the Queue Connection Factory referenced here is
    // specified by the mappedName attribute of @Resource,
    // which can be overwritten in weblogic-ejb-jar.xml.
    @Resource(name = "jms/mdbQCF", mappedName = "weblogic.examples.ejb30.QueueConnectionFactory")
    private QueueConnectionFactory mdbQCF;
    // The JNDI name of the Queue referenced here is specified by the mappedName
    // attribute of @Resource,
    // which can be overwritten in weblogic-ejb-jar.xml.
    @Resource(name = "jms/mdbQueue", mappedName = "weblogic.examples.ejb30.ExampleQueue")
    private Queue mdbQueue;
    private QueueConnection qcon = null;
    private QueueSession qsession = null;
    private QueueSender qsender = null;

    @PostConstruct
    private void init()
    {
      try
      {
        qcon = mdbQCF.createQueueConnection();
        qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
        qsender = qsession.createSender(mdbQueue);
      }
      catch (JMSException e)
      {
        throw new RuntimeException(e);
      }
    }

    public void reviewAdded(Review review)
    {
      try
      {
        ObjectMessage msg = qsession.createObjectMessage();
        msg.setIntProperty("reviewid", review.getId());
        qcon.start();
        qsender.send(msg);
      }
      catch (JMSException e)
      {
        throw new RuntimeException(e);
      }
    }

    @PreDestroy
    void cleanup()
    {
      // close sender, session and connection
    }
  }
From the code, the thing that links the MDB and the sender seems to be the queue named "weblogic.examples.ejb30.ExampleQueue" which is referenced to by both the MDB and the sender.

Use MDB in Web Application

First, the web.xml defines some references to the EJB.
<ejb-ref>
   <ejb-ref-name>ejb/ReviewManager<ejb-ref-name>
   <ejb-ref-type>Session</ejb-ref-type>
   <remote>examples.ejb.ejb30.service.ReviewService</remote>
   <ejb-link>domain.jar#ReviewManagerBean</ejb-link>
   </ejb-ref>
Here domain.jar is the jar file that contains the ReviewManagerBean.class and the file persistence.xml. The jar is the ejb module. The jsp and servlet files below are in a war file that is the web module.

In JSP code, it first gets a session bean ReviewManagerBean using JNDI and calls the business method of the bean.
<%
String SERVICE_NAME = "ReviewManagerBean";
ReviewService service = (ReviewService) session.getAttribute(SERVICE_NAME);
if (service == null) {
   Context ctx = new InitialContext();
   service = (ReviewService) ctx.lookup("java:comp/env/ejb/ReviewManager");
   session.setAttribute(SERVICE_NAME, service);
}
......
service.doSomeBusiness();
%>
The session bean ReviewManagerBean is injected a session bean ReviewListener, which is the message sender. The business method doSomeBusiness() of ReviewManagerBean will call the business method of the ReviewListener to send out the messages.
@Stateless(mappedName="ReviewService")
@Remote(ReviewService.class)
public class ReviewManagerBean extends ReviewServiceImpl
implements ReviewService {
@PersistenceContext(unitName = "reviewSession")
private EntityManager em;
@EJB ReviewListener listener;
The business method of ReviewManagerBean can be as follows:
pubilc void doSomeBusiness(){
// use the EntityManager em to do something
// use the ReviewListener listener to do something
}
A couple of notes here. (1) The method doSomeBusiness() uses both the EntityManager em and the session bean ReviewListener. So the transaction is cross both the database and JMS. (2) The jsp uses a session bean "A" which itself is injected a session bean "B". The jsp calls the business method of the session bean "A" which in turn calls a business method of "B". So there should be a transaction propagation here. (3) When the sender sends the message, is the MDB class loaded into the JVM already? Is there any configuration needed for this?

No comments:

Post a Comment