Efficient Lightweight JMS with Spring and ActiveMQ

来源:互联网 发布:淘宝图书经营许可共用 编辑:程序博客网 时间:2024/04/23 18:45

From: http://codedependents.com/2009/10/16/efficient-lightweight-jms-with-spring-and-activemq/

Asynchronicity, its the number one design principal for highly scalable systems, and for Java that means JMS, which in turn means ActiveMQ. Buthow do I use JMS efficiently? One can quickly  become overwhelmed with talk of containers, frameworks, and a plethora of options, most of which are outdated. So lets pick it apart.

Frameworks

The ActiveMQ documentation makes mention of two frameworks; Camel andSpring. The decision here comes down to simplicity vs functionality. Camel supports an immense amount ofEnterprise Integration Patterns that can greatly simplify integrating a variety of services and orchestrating complicated message flows between components. Its certainly a best of breed if your system requires such functionality. However, if you are looking for simplicity and support for the basic best practices then Spring has the upper hand. For me, simplicity wins out any day of the week.

JCA (Use It Or Loose It)

Reading through ActiveMQ’s spring support one is instantly introduced to the idea of a JCA container and ActiveMQ’s various proxies and adaptors for working inside of one. However, this is all a red herring. JCA is part of the EJB specification and as with most of the EJB specification, Spring doesn’t support it. Then there is a mention ofJencks, a “lightweight JCA container for Spring”, which was spun off of ActiveMQ’sJCA container. At first this seems like the ideal solution, but let me stop you there.  Jencks was last updated on January 3rd 2007. At that time ActiveMQ was at version 4.1.x and Spring was at version 2.0.x and things have come a long way, a very long way. Even trying to get Jencks from the maven repository fails due to dependencies on ActiveMQ 4.1.x jars that no longer exist. The simple fact is there are better and simpler ways to ensure resource caching.

Sending Messages

The core of Spring’s message sending architecture is the JmsTemplate. In typical Spring template fashion, the JmsTemplate abstracts away all the cruft of opening and closing sessions and producers so all the application developer needs to worry about is the actual business logic. However, ActiveMQ is quick to point out the JmsTemplate gotchas, mostly that JmsTemplate is designed to open and close the session and producer on each call. To prevent this from absolutely destroying the messaging performance the documentation recommends using ActiveMQ’s PooledConnectionFactory which caches the sessions and message producers. However this too is outdated. Starting with version 2.5.3, Spring started shipping its own CachingConnectionFactory which I believe to be the preferred caching method. (UPDATE: Inmy more recent post, I talk about when you might want to use PooledConnectionFactory.) However, there is one catch to point out. By default the CachingConnectionFactory only caches one session which the javadoc claims to be sufficient for low concurrency situations. By contrast, the PooledConnectionFactorydefaults to 500. As with most settings of this type, some amount of experimentation is probably in order. I’ve started with 100 which seems like a good compromise.

Receiving Messages

As you may have noticed, the JmsTemplate gotchas strongly discourages using the recieve() call on the JmsTemplate, again, since there is no pooling of sessions and consumers.  Moreover, all calls on the JmsTemplate are synchronous which means the calling thread will block until the method returns. This is fine when using JmsTemplate to send messages since the method returns almost instantly. However, when using the recieve() call, the thread will block until a message is received, which has a huge impact on performance. Unfortunately, neither the JmsTemplate gotchas nor thespring support documentation mentions the simple Spring solution for these problems. In fact they both recommend using Jencks, which we already debunked. The actual solution, using the DefaultMessageListenerContainer, is buried in the how do I use JMS efficiently documentation. The DefaultMessageListenerContainer allows for the asynchronous receipt of messages as well as caching sessions and message consumers. Even more interesting, the DefaultMessageListenerContainer can dynamically grow and shrink the number of listeners based on message volume. In short, this is why we can completely ignore JCA.

Putting It All Together

Spring Context XML

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:amq="http://activemq.apache.org/schema/core"xmlns:jms="http://www.springframework.org/schema/jms"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.2.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsdhttp://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-2.5.xsd"><!-- enables annotation based configuration --><context:annotation-config /><!-- scans for annotated classes in the com.company package --><context:component-scan base-package="com.company"/><!-- allows for ${} replacement in the spring xml configuration from the system.properties file on the classpath --><context:property-placeholder location="classpath:system.properties"/><!-- creates an activemq connection factory using the amq namespace --><amq:connectionFactory id="amqConnectionFactory" brokerURL="${jms.url}" userName="${jms.username}" password="${jms.password}" /><!-- CachingConnectionFactory Definition, sessionCacheSize property is the number of sessions to cache --><bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">    <constructor-arg ref="amqConnectionFactory" />    <property name="exceptionListener" ref="jmsExceptionListener" />    <property name="sessionCacheSize" value="100" /></bean><!-- JmsTemplate Definition --><bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">   <constructor-arg ref="connectionFactory"/></bean><!-- listener container definition using the jms namespace, concurrency is the max number of concurrent listeners that can be started --><jms:listener-container concurrency="10" >    <jms:listener id="QueueListener" destination="Queue.Name" ref="queueListener" /></jms:listener-container></beans>

There are two things to notice here. First, I’ve added the amq and jms namespaces to the opening beans tag. Second, I’m using the Spring 2.5annotation based configuration. By using the annotation based configuration I can simply add@Component annotation to my Java classes instead of having to specify them in the spring context xml explicitly. Additionally, I can add@Autowired on my constructors to have objects such as JmsTemplate automatically wired into my objects.

QueueSender

package com.company;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jms.core.JmsTemplate;import org.springframework.stereotype.Component;@Componentpublic class QueueSender{    private final JmsTemplate jmsTemplate;    @Autowired    public QueueSender( final JmsTemplate jmsTemplate )    {        this.jmsTemplate = jmsTemplate;    }    public void send( final String message )    {        jmsTemplate.convertAndSend( "Queue.Name", message );    }}

Queue Listener

package com.company;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.TextMessage;import org.springframework.stereotype.Component;@Componentpublic class QueueListener implements MessageListener{    public void onMessage( final Message message )    {        if ( message instanceof TextMessage )        {            final TextMessage textMessage = (TextMessage) message;            try            {                System.out.println( textMessage.getText() );            }            catch (final JMSException e)            {                e.printStackTrace();            }        }    }}

JmsExceptionListener

package com.company;import javax.jms.ExceptionListener;import javax.jms.JMSException;import org.springframework.stereotype.Component;@Componentpublic class JmsExceptionListener implements ExceptionListener{    public void onException( final JMSException e )    {        e.printStackTrace();    }}

Update

I have finally updated the wiki at activemq.apache.org. The following pages now recommend using MessageListenerContainers and JmsTemplate with a Pooling ConnectionFactory instead of JCA and Jencks.
  • http://activemq.apache.org/spring-support.html
  • http://activemq.apache.org/how-do-i-use-jms-efficiently.html
  • http://activemq.apache.org/jmstemplate-gotchas.html


原创粉丝点击