Using Lingo with Spring 2.0 Message Driven POJOs
Following the example on the Lingo website, I set up a simple service and exposed it through JMS. Using Lingo, you can make RPC style (as well as asynchronous) calls to services using JMS as the transport. For my sample application, I used Tibco JMS (E4JMS) as the provider.
For the server side, I did this:
<beans>
<!-- the server side -->
<bean id="server" class="org.logicblaze.lingo.jms.JmsServiceExporter">
<property name="service" ref="serverImpl"/>
<property name="serviceInterface" value="ets.lingotest.Reverse"/>
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="requestDestination"/>
</bean><bean id="serverImpl" class="ets.lingotest.ReverseServerImpl" singleton="true"/>
<!-- JMS ConnectionFactory to use -->
<bean id="jmsFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>GenericConnectionFactory</value>
</property>
</bean><bean id="requestDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>queue.sample</value>
</property>
</bean>
</beans>
And the client looks like this:
<beans>
<!-- client side proxy-->
<bean id="client" class="org.logicblaze.lingo.jms.JmsProxyFactoryBean">
<property name="serviceInterface" value="ets.lingotest.Reverse"/>
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="requestDestination"/>
</bean><!-- JMS ConnectionFactory to use -->
<bean id="jmsFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>GenericConnectionFactory</value>
</property>
</bean><bean id="requestDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>queue.sample</value>
</property>
</bean>
</beans>
And here is the interface being exposed:
public interface Reverse {
public String reverse(String s);
}
With this setup, a single MessageListener is set up to listen on the queue. Easy setup, but not very scalable. To do a proper setup, you can use Jencks to pool JMS resources and process the messages asynchronously.
Another option with Spring 2.0 is to use the new asynchronous JMS processing classes. These classes create and manage threads that hold connections to the JMS queue. For my example, I will use DefaultMessageListenerContainer.
I make this change to the server application context:
<beans>
<!-- the server side -->
<bean id="server" class="org.logicblaze.lingo.jms.JmsServiceExporter">
<property name="service" ref="serverImpl"/>
<property name="serviceInterface" value="ets.lingotest.Reverse"/>
<property name="connectionFactory" ref="jmsFactory"/>
<!--<property name="destination" ref="requestDestination"/>-->
</bean><bean id="serverImpl" class="ets.lingotest.ReverseServerImpl" singleton="true"/>
<!-- JMS ConnectionFactory to use -->
<bean id="jmsFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>GenericConnectionFactory</value>
</property>
</bean><bean id="requestDestination" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>queue.sample</value>
</property>
</bean><!--Spring async message processing -->
<bean id="messageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="destination" ref="requestDestination"/>
<property name="messageListener" ref="server"/>
<property name="concurrentConsumers" value="5"/>
</bean></beans>
The JmsServiceExporter no longer has the destination property configured; instead it is set as the messageListener property of DefaultMessageListenerContainer. Note the DefaultMessageListenerContainer knows all about the JMS connection and queue where the messages will be coming in. It also has the concurrentConsumers value set to 5, so there will be 5 threads listening for messages.
Previously, the server output was:
ReverseServerImpl [E4JMS Session Dispatcher (171)] INFO - Server got string: hello world
ReverseServerImpl [E4JMS Session Dispatcher (171)] INFO - Server returning: dlrow olleh
Now it is
ReverseServerImpl [DefaultMessageListenerContainer0] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer0] INFO - Server returning: dlrow olleh
Note that it is now DefaultMessageListenerContainer0 handling the message instead of E4JMS Session Dispatcher. I also noticed that the message processing is set up round robin, so executing the client multiple times yields:
ReverseServerImpl [DefaultMessageListenerContainer0] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer0] INFO - Server returning: dlrow olleh
ReverseServerImpl [DefaultMessageListenerContainer1] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer1] INFO - Server returning: dlrow olleh
ReverseServerImpl [DefaultMessageListenerContainer2] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer2] INFO - Server returning: dlrow olleh
ReverseServerImpl [DefaultMessageListenerContainer3] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer3] INFO - Server returning: dlrow olleh
ReverseServerImpl [DefaultMessageListenerContainer4] INFO - Server got string: hello world
ReverseServerImpl [DefaultMessageListenerContainer4] INFO - Server returning: dlrow olleh
This setup is pretty simple, but I’m not sure what the implications are as far as performance and scalability, especially given the usage patterns of JmsTemplate as documented by James Strachan. I’m curious to hear from anyone that has a similar setup or from anyone that has suggestions/feedback.