您的位置:首页 > 编程语言 > Java开发

Efficient Lightweight JMS with Spring and ActiveMQ

2012-08-21 09:58 274 查看
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. But
how 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 and
Spring. The decision here comes down to simplicity vs functionality. Camel supports an immense amount of
Enterprise 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 of
Jencks, a “lightweight JCA container for Spring”, which was spun off of ActiveMQ’s
JCA 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: In

my 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 PooledConnectionFactory

defaults 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 the
spring 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.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://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.5

annotation 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;

@Component
public 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;

@Component
public 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;

@Component
public 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: