Thursday, April 1, 2010

In my previous post, I introduced a general-purpose, allegedly reusable Spring configuration for an ActiveMQ-based JMS application. In this post, I'll expand on that by writing a unit test around a basic configuration that establishes an evolving prototype.

I don't need to tell you that, without any other work, this test will fail; let's take on something a bit less trivial. The test assumes a Spring configuration which resides in the same logical directory tree as the class. Here's that spring-jms-demo.xml configuration:

Now the unit test will run successfully; there's no (meaningful) output to show you, and for that matter the test and listeners are along the lines of "what's thesimplestthing thatwouldwork" - it's really only done to demonstrate that all the moving parts are in place and that I've used them correctly so far. In my next, I'll start adding on some target functionality and look for ways to test it.

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.

Welcome to the Perimeter Sweep Blog

My blog is largely intended to be a placeholder for topics involving software development - architecture, technology drill-downs, best practices, various solutions, workarounds, gotchas and the like - things that will remind me what I've learned over time. If it helps you out also - all the better.

Subscribe To This Blog

About Me

I'm a Senior Software Engineer, an avid runner, and formerly a professional musician...currently the proud father of a super-tyke, raising two Siberian Huskies and married to my best friend. Life is good.