Thursday, April 1, 2010

A General Template for Spring-JMS Applications

Here I'll post a simple Spring setup that can be reused for specific JMS-based applications. It assumes the use of ActiveMQ 5.3.0, opting to use Spring's caching connection factory in lieu of ActiveMQ's pooling version. It uses various Spring-JMS components, including one JmsTemplate and one MessageListenerContainer, respectively intended for sending and receiving messages to-and-from a topic. In upcoming posts, I'll use this to prototype a JMS application.

The connection factory and JMS template have most of their specific configuration parameters externalized to facilitate better reuse.

For the ActiveMQ connection factory, the externalized properties include the broker URL, use of asynchronous sends and choice around dispatching asynchronously. The example XML template that follows includes some commentary around the pros/cons of configuring the last two. This factory is used by Spring's caching connection factory, externalizes configuration of session cache size, and assumes the presence of an exception listener.

The JMS template has the following properties that are configurable: delivery mode, time-to-live, whether or not explicit quality of service is enabled, whether or not the session is transacted, whether or not a given connection should listen for messages sent by that same connection, and whether or not the domain is pub-sub or point-to-point.

I would have externalized configuration for the listener container but, as it turns out, apparently the listener-container parent tag does not support this, so I've hardwired this to use the default listener container, listening on a topic within a transaction.

Here's that template:

<?xml version="1.0" encoding="UTF-8"?>
<!--
A reusable template for Spring-JMS applications. The following must be configured externally, presumably
via Spring's property-placeholder mechanism:

brokerURL
useAsyncSend
dispatchAsync
sessionCacheSize
deliveryMode
explicitQosEnabled
sessionTransacted
pubSubNoLocal
pubSubDomain
destination

As well, the following is assumed:

An exception listener that can be dereference by the name 'myExceptionListener' is assumed present 
in classpath.
A message listener that can be dereference by the name 'myListener' is assumed present in classpath.
-->

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://activemq.apache.org/schema/core
           http://activemq.apache.org/schema/core/activemq-core-5.3.0.xsd
           http://www.springframework.org/schema/jms
           http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">

    <!--
    Configure connection factory
    -->
    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <!--
        for testing, we want to use vm://localhost?async=false&broker.persistent=false.
        This does intra-JVM messaging using synchronous sends and doesn't bother with persisting
        messages to cover provider failures. See http://activemq.apache.org/uri-protocols.html for
        protocol options.
         -->
        <property name="brokerURL" value="${testUrl}"/>
        <!--
         See http://activemq.apache.org/async-sends.html - use async sends for better throughput but
         only if you're able to tolerate some message loss. 

         From the FUSE Tuning Guide (http://fusesource.com/wiki/display/ProdInfo/FUSE%20Message%20Broker%20Performance%20Tuning%20Guide):

         If you are using persistent messaging and you don't want to use Async Sends (see below) then you should
         use JMS transactions to batch up many operations inside a single transaction. If you enable Async Sends
         then you will reduce the blocking in senders which increases throughput.

         The only downside of asynchronous sending is if the send fails for whatever reason (security exception
         typically or some transport failure), then you don't get an exception thrown in the sender thread,
         since all the work is done asynchronously, though your ErrorListener will get notified.

         From the activemq.xsd:

         Forces the use of Async Sends which adds a massive performance boost; but means that the send() method will
         return immediately whether the message has been sent or not which could lead to message loss.
         -->
        <property name="useAsyncSend" value="${useAsyncSend}"/>
        <!--
        From the activemq.xsd:

        Enables or disables the default setting of whether or not consumers have their messages dispatched
        synchronously or asynchronously by the broker. For non-durable topics for example we typically dispatch
        synchronously by default to minimize context switches which boost performance. However sometimes its better
        to go slower to ensure that a single blocked consumer socket does not block delivery to other consumers.
        See http://activemq.apache.org/consumer-dispatch-async.html.
        -->
        <property name="dispatchAsync" value="${dispatchAsync}"/>
    </bean>

    <!--
    Use the vanilla connection factory to configure a caching connection factory, using the Spring
    version instead of ActiveMQ. Default session cache size for Spring factory is 1. An exception listener
    that can be dereference by the name 'myExceptionListener' is assumed present in classpath.
    -->
    <bean id="cachingConnectionFactory"
          class="org.springframework.jms.connection.CachingConnectionFactory"
          destroy-method="destroy">

        <property name="targetConnectionFactory" ref="connectionFactory"/>
        <property name="sessionCacheSize" value="${sessionCacheSize}"/>
        <property name="exceptionListener" ref="myExceptionListener"/>

    </bean>

    <!--
    The JMS template intended for sending of messages.
    -->
    <bean id="myTemplate" class="org.springframework.jms.core.JmsTemplate">

        <constructor-arg ref="cachingConnectionFactory"/>
        <property name="deliveryMode" value="${deliveryMode}"/>
        <property name="timeToLive" value="${timeToLive}"/>
        <!--
        if explicit QoS is enabled, you can control deliveryMode, priority and TTL on a per-send basis
        -->
        <property name="explicitQosEnabled" value="${explicitQosEnabled}"/>
        <property name="sessionTransacted" value="${sessionTransacted}"/>
        <property name="pubSubNoLocal" value="${pubSubNoLocal}"/>
        <!--
        override JMS template default domain type of PTP if you want pub-sub
        -->
        <property name="pubSubDomain" value="${pubSubDomain}"/>
    </bean>

    <!--
    concurrency should be 1 for topic listeners. This is one attribute that apparently canNOT be
    externalized into the properties file. Use transacted acknowledgment (another attribute that
    cannot be externalized). A message listener that can be dereference by the name 'myListener' 
    is assumed present in classpath.
    -->
    <jms:listener-container container-type="default"
                            destination-type="topic"
                            acknowledge="transacted"
                            connection-factory="cachingConnectionFactory"
                            concurrency="1">
        <jms:listener
                destination="${myTopic}"
                ref="myListener"/>
    </jms:listener-container>

</beans>

That's a starting point; from here, I'll add configuration, listeners and a test case as the next step.

References

Apache ActiveMQ
Spring 3.0.0 Javadoc
ActiveMQ Javadoc
FUSE Tuning Guide
Spring 3.0.0 Reference Documentation
Efficient Lightweight JMS with Spring and ActiveMQ
Creating Robust JMS Applications (Java EE 5 Tutorial)

No comments:

Post a Comment