Message-driven POJOs revisited - No Fluff Just Stuff

Message-driven POJOs revisited

Posted by: Craig Walls on July 22, 2005

In a previous installment, I showed you how I used ActiveMQ's JCAContainer to expose a class that only implements javax.jms.MessageListener could be exposed as a message-driven POJO (hereafter referred to as MDP).

Aside from sparking a heated and unnecessary JBoss vs. Spring debate on The ServerSide (which I refuse to participate in), that article demonstrated that a simple JavaBean could be granted the powers of message-driven EJBs without requiring an EJB container or the lifecycle methods mandated by javax.ejb.MessageDrivenBean.

In that article, I questioned the validity of referring to the MDP as a "POJO" because it still must implement javax.jms.MessageListener. I wrote it off as a minor annoyance at the time. Since then, James has been quite helpful and has shown me how to achieve MDPs with pure POJOs (not requiring any external interfaces of any kind). The trick is to use Lingo. Let me show you how...

Getting started

As before, there are several JARs you'll need in your classpath. The following list is what I'm using to develop and implement Lingo-style MDPs:

  • spring.jar (I'm using the 1.2.2 version)
  • log4j-1.2.9.jar
  • commons-logging-1.0.3.jar
  • activemq-core-3.1-SNAPSHOT.jar
  • concurrent-1.3.4.jar
  • geronimo-spec-jms-1.1-rc4.jar
  • geronimo-spec-j2ee-management-1.0-rc4.jar
  • lingo-1.0-SNAPSHOT.jar

As a service to my readers, I've bundled up all of these JARs along with my example code and made it available here.

Writing the POJO

The first thing you'll need for a MDP is a POJO. The following HelloServiceImpl class mimics the functionality of the MDP from my previous article:

  package com.habuma.mdpojo.lingo;

  public class HelloServiceImpl implements HelloService {
    public void sayHello(String name) {
      System.out.println("Hello " + name + "!");
    }
}

You'll notice that (unlike my previous example) this class doesn't implement any interface from an external framework or platform. In fact, the only interface it implements is its own service interface:

  package com.habuma.mdpojo.lingo;

  public interface HelloService {
    public void sayHello(String name);
  }

So far there's nothing special about this POJO. In fact, it could be used as a regular non-MDP bean if you'd like. That's the beauty of the Lingo approach: Your MDP doesn't even know that it's message-driven!

Exporting the JMS service

Turning the POJO into an MDP involves a service exporter. If you've ever used Spring to export POJOs as RMI, Hessian/Burlap, or HttpInvoker services, you're already familiar with service exporters. (If not, then chapter 6 of Spring in Action may be of interest to you.) In the case of Lingo MDPs, the service exporter is Lingo's JmsServiceExporter. I've declared the JmsServiceExporter bean (in mdp-service.xml) as follows:

  <bean id="server"
      class="org.logicblaze.lingo.jms.JmsServiceExporter">
    <property name="service">
      <bean class="com.habuma.mdpojo.lingo.HelloServiceImpl" />
    </property>
    <property name="serviceInterface" 
        value="com.habuma.mdpojo.lingo.HelloService" />
    <property name="connectionFactory">
      <ref bean="connectionFactory" />
    </property>
    <property name="destination">
      <ref bean="destination" />
    </property>
  </bean>

The "service" property tells the JmsServiceExporter which POJO will be exported. Here I've wired HelloServiceImpl in as an inner-bean, but there's no reason it couldn't have been declared as a regular <bean> and wired in as a reference. The "serviceInterface" tells the JmsServiceExporter the interface of the exposed POJO.

The "connectionFactory" and "destination" properties wire in the JMS connection factory and a message queue that the service should be exposed to. Assuming that ActiveMQ will be running on the localhost, listening on port 61616, the following beans will do:

  <bean id="connectionFactory"
      class="org.activemq.ActiveMQConnectionFactory">
    <property name="brokerURL" value="tcp://localhost:61616" />
  </bean>
  
  <bean id="destination"
      class="org.activemq.message.ActiveMQQueue">
    <constructor-arg index="0" value="Hello.Queue" />
  </bean>

The same "connectionFactory" and "destination" beans will also be needed on the client side. So that I don't have to duplicate these declarations, I've placed them in a separate Spring application context file called "mdp-common.xml" that will be shared by both the MDP and the client.

Starting the MDP

With JmsServiceExporter declared, we're ready to start up the application context. The following class will do the trick:

  public class StartMDPojo {
    public static void main(String[] args) {
      new FileSystemXmlApplicationContext(
          new String[] {"mdp-common.xml", "mdp-service.xml"});
    }
  }

Make sure ActiveMQ is running, then fire up StartMDPojo. If everything goes well, your MDP will be added as a new client to ActiveMQ and will be ready to service requests. (Note: I had to use ActiveMQ 3.1-SNAPSHOT to get this to work. I had some trouble running Lingo against ActiveMQ 3.0.)

Sending a message

Sending a message to the MDP is also fairly painless. Just as with accessing other remoting options in Spring, we'll use a proxy factory bean. In this case, we'll use Lingo's JmsProxyFactoryBean (declared in mdp-client.xml):

  <bean id="client"
      class="org.logicblaze.lingo.jms.JmsProxyFactoryBean">
    <property name="serviceInterface" 
        value="com.habuma.mdpojo.lingo.HelloService"/>
    <property name="connectionFactory" ref="connectionFactory" />
    <property name="destination" ref="destination" />
  </bean>

If you've used any of Spring's proxy factory beans for accessing other remote services (such as RmiProxyFactoryBean or HessianProxyFactoryBean), then JmsProxyFactoryBean should seem somewhat familiar. Just as with the other remoting options, JmsProxyFactoryBean wraps the details of accessing the remote service in a proxy that implements the POJO's service interface. From the client's perspective, the remote service appears to be just another local POJO.

The properties used to configure JmsProxyFactoryBean are fairly self-explanatory. Again, the "serviceInterface" defines the interface that the proxy will implement. And the "connectionFactory" and "destination" properties tell the proxy which message queue to send the message to.

In a typical application, the "client" bean would be wired into a HelloService property of some other bean. But for the sake of simplicity, our example will pull the "client" directly out of the application context and call it's sayHello() method:

  public class MDPojoClient {
    public static void main(String[] args) {
      ApplicationContext ctx = new
          FileSystemXmlApplicationContext(
              new String[] {"mdp-common.xml", "mdp-client.xml"});

      HelloService client = (HelloService) ctx.getBean("client");

      client.sayHello("Craig");
    }
  }

Notice that the MDPojoClient isn't any the wiser that the "client" bean that it is using is actually an MDP. For all it knows, it's dealing with some local POJO that implements HelloService. In fact, the only place you'll find any references to JMS is in the configuration files. The actual Java source code is blissfully ignorant of the fact that there's any JMS work going on behind the scenes. This is important because it makes it easy to swap out the MDP implementation with some other implementation (perhaps an RMI implementation, an EJB implementation...or even a mock implementation when unit-testing).

When you run MDPojoClient and the sayHello() method is invoked, the inner-workings of the JMS proxy will place a message into the "Hello.Queue". On the MDP side, the service exporter will consume the message and send it on to the actual implementation of sayHello().

One-way methods

If you were paying close attention, you may have noticed that the call to sayHello() blocked--that is, it was a synchronous call. Can Lingo not do asynchronous calls?

Lingo decides whether a method is synchronous or asynchronous by consulting a Metadata Strategy. The default (and currently the only) metadata strategy is SimpleMetadataStrategy. By default, SimpleMetadataStrategy assumes that all calls are synchronous. But by configuring a new LingoRemoteInvocationFactory with your JmsProxyFactoryBean, you can change all of that. What you need to do is register a LingoRemoteInvocationFactory bean in mdp-client.xml like this:

  <bean id="invocationFactory"
  class="org.logicblaze.lingo.LingoRemoteInvocationFactory">
    <constructor-arg>
      <bean class="org.logicblaze.lingo.SimpleMetadataStrategy">
        <constructor-arg value="true" />
      </bean>
    </constructor-arg>
  </bean>

As you can see, I've told Spring to instantiate LingoRemoteInvocationFactory using a single-argument constructor, passing a SimpleMetadataStrategy instance. The SimpleMetadataStrategy itself is declared as an inner-bean. Its "oneWayForVoidMethods" property is set to "true" to indicate that void methods are one-way and shouldn't block.

Just declaring the "invocationFactory" bean is not quite enough. You must also wire it into the "remoteInvocationFactory" property of the JmsProxyFactoryBean:

  <bean id="client"
      class="org.logicblaze.lingo.jms.JmsProxyFactoryBean">
    <property name="serviceInterface" 
        value="com.habuma.mdpojo.lingo.HelloService"/>
    <property name="connectionFactory" ref="connectionFactory" />
    <property name="destination" ref="destination" />
    
    <property name="remoteInvocationFactory" ref="invocationFactory" />
  </bean>

Now if you run MDPojoClient the call will return immediately, even if the sayHello() method takes longer to execute. Try it...put a Thread.sleep(10000) in the sayHello() method and some appropriate logging to see that the call returns immediately while the sayHello() method stalls for 10 seconds.

But what about...?

What if the method isn't void? Can the call still be asynchronous? In short, no. If the method being invoked returns something, the client must wait for the return value. (In theory, you could write your own metadata strategy to ignore returned values, but I'm not sure of what value that would be.)

What if you need an asynchronous method to reply to the client? All you must do is create an instance of java.util.EventListener, pass it to the one-way method as an argument, then have the one-way method place its results into the listener. (Perhaps I'll write up an article showing this in more detail some other time).

What about JBoss message-driven POJOs? How do they compare to the Lingo approach? Well, I've only glanced over the Trailblazer examples, so I can't speak as an expert on the topic. I'm still forming my opinions at this point, but I'm a bit troubled that a supposed "POJO" approach requires me to use a org.jboss.annotation.ejb.Consumer annotation. What about using this annotation is better than implementing javax.jms.MessageListener? (Don't read this question as an attack on JBoss...I'm legitimately asking.)


Before I go, one other nit: There are some out there who read my last article and referred to the MDP technique as the "Spring approach". It is true that Spring is involved, but to be precise, the technique presented in that article is actually the ActiveMQ approach to MDP. Likewise, the technique presented in this article is the Lingo approach to MDP. Spring itself currently does not have its own MDP solution.

As I mentioned before, I'll be discussing MDPs (both ActiveMQ-style and Lingo-style) among other things in a presentation that I'll be giving at the LoneStar Software Symposium in Austin, TX next month. Be sure to say "hi" if you're there.

Any typos, goof-ups, blunders, snaffoos, or bloopers contained in this article are merely a figment of your imagination. Boo-boos and flub-ups, however, are probably my fault. Please let me know if you find any so that I may correct them.

Craig Walls

About Craig Walls

Craig Walls is a Principal Engineer, Java Champion, Alexa Champion, and the author of Spring AI in Action, Spring in Action, and Build Talking Apps. He's a zealous promoter of the Spring Framework, speaking frequently at local user groups and conferences and writing about Spring. When he's not slinging code, Craig is planning his next trip to Disney World or Disneyland and spending as much time as he can with his wife, two daughters, 1 bird and 2 dogs.

Why Attend the NFJS Tour?

  • » Cutting-Edge Technologies
  • » Agile Practices
  • » Peer Exchange

Current Topics:

  • Languages on the JVM: Scala, Groovy, Clojure
  • Enterprise Java
  • Core Java, Java 8
  • Agility
  • Testing: Geb, Spock, Easyb
  • REST
  • NoSQL: MongoDB, Cassandra
  • Hadoop
  • Spring 4
  • Cloud
  • Automation Tools: Gradle, Git, Jenkins, Sonar
  • HTML5, CSS3, AngularJS, jQuery, Usability
  • Mobile Apps - iPhone and Android
  • More...
Learn More »