1:环境和版本
java:jdk7
spring:4.1.3
activemq:5.8.0
2:spring与activeMQ的结合配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd
">
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61636"></property>
</bean>
<!-- ActiveMQ为我们提供了一个PooledConnectionFactory,往里面注入一个ActiveMQConnectionFactory可以用来将Connection,
Session和MessageProducer池化,这样可以大大的减少我们的资源消耗。
问题:使用poolConnectionFactory时候,用JMSTemplate同步循环接收消息,因为JMSTemplate会自动在接收消息后关闭连接,
所以循环到第二次的时候会报错,这个问题待解决
问题:使用poolConnectionFactory时候,用监听来接收消息,会有部分消息残留在队列里面,问题待解决
结论:还是先别用连接池了-->
<bean id="poolConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" >
<property name="connectionFactory" ref="activeMQConnectionFactory" />
<property name="maxConnections" value="10"/>
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory 这里我使用的是singleConnectionFactory-->
<bean id="singleConnectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="activeMQConnectionFactory"/>
</bean>
<bean id="cachingConnectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="activeMQConnectionFactory"/>
</bean>
<!-- 配置生产者 -->
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="senderJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="singleConnectionFactory"/>
<!-- NON_PERSISTENT非持久化 1 ,PERSISTENT持久化 2 -->
<property name="deliveryMode" value="2"/>
<property name="sessionTransacted" value="true"/>
<property name="sessionAcknowledgeModeName" value="AUTO_ACKNOWLEDGE"/>
</bean>
<!--这个是队列目的地,点对点的 -->
<bean id="activeMQQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="FirstQueue"/>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic"/>
</bean>
<!-- 自定义消费者 -->
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="receiverJmsTemplate" class="com.system.freemwork.amq.SimpleJmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="singleConnectionFactory"/>
<!-- 如果是原生的amq创建的session,将session设置为true时候,ack会固定被设置为AUTO_ACKNOWLEDGE
所以想要手动确认,那么session的事物必须设置为false,并且ack设置为CLIENT_ACKNOWLEDGE -->
<property name="sessionTransacted" value="false"/>
<property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE"/>
<property name="receiveTimeout" value="1000"/>
<property name="autoAcknowledge" value="true"/>
</bean>
</beans>
3:编写send测试类
package com.test.spring;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
/**
* 类描述:sender测试类
*
* @author fengyong
* @version 1.0
* @since 1.0
* Created by fengyong on 16/8/3 下午7:45.
*/
public class ActiveMqSender extends BaseTest {
@Autowired
private JmsTemplate senderJmsTemplate;
@Test
public void activeMq(){
for(int i = 1;i<=10;i++){
senderJmsTemplate.convertAndSend("FirstQueue","我是第"+i+"个");
}
System.out.print("全部执行完毕!!!");
}
}
4:编写receiver测试类
package com.test.spring;
import com.system.freemwork.amq.SimpleJmsTemplate;
import org.apache.activemq.command.ActiveMQQueue;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.JMSException;
import javax.jms.TextMessage;
/**
* 类描述:receiver测试类
*
* @author fengyong
* @version 1.0
* @since 1.0
* Created by fengyong on 16/8/8 下午5:28.
*/
public class ActiveMqReceiver extends BaseTest{
@Autowired
private SimpleJmsTemplate receiverJmsTemplate;
@Autowired
private ActiveMQQueue activeMQQueue;
/**
* 坑爹的方法,如果session事物设置为true,receiver直接将sessioin进行commit,
*
* 如果设置为false,receiver方法会直接判断进行消息确认,无法做到手动的消息确认,所以一旦发生异常,这条消息不会回到消息队列中
*
* session的提交可以认为是消息确认收到
* @throws JMSException
*/
@Test
public void receiver() throws JMSException {
int i=1;
while (true){
i++;
TextMessage message = (TextMessage)receiverJmsTemplate.receive(activeMQQueue);
if (null != message) {
System.out.println("收到消息==================" + message.getText());
} else {
System.out.print("超时10秒");
break;
}
}
}
}
注:如果session事物设置为true,receiver直接将sessioin进行commit.源码如下
if (session.getTransacted()) { // Commit necessary - but avoid commit call within a JTA transaction. if (isSessionLocallyTransacted(session)) { // Transacted session created by this template -> commit. JmsUtils.commitIfNecessary(session); } }
如果session事物设置为false,receiver方法会直接判断进行消息确认,无法做到手动的消息确认,所以一旦发生异常,这条消息不会回到消息队列中.源码如下
else if (isClientAcknowledge(session)) { // Manually acknowledge message, if any. if (message != null) { message.acknowledge(); } }
所以需要修改源码不让其在receiver的时候自动确认收到消息
5:新建SimpleJmsTemplate继承JmsTemplate
package com.system.freemwork.amq;
import org.springframework.jms.JmsException;
import org.springframework.jms.connection.ConnectionFactoryUtils;
import org.springframework.jms.connection.JmsResourceHolder;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.SessionCallback;
import org.springframework.jms.support.JmsUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import sun.misc.resources.Messages_ja;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
/**
* 类描述:自定义JmsTemplate,实现客户手动确认
*
* @author fengyong
* @version 1.0
* @since 1.0
* Created by fengyong on 16/8/10 上午10:03.
*/
public class SimpleJmsTemplate extends JmsTemplate {
private final JmsTemplateResourceFactory transactionalResourceFactory = new JmsTemplateResourceFactory();
/**
* 是否开启手动确认标记
*/
private Boolean autoAcknowledge;
MessageConsumer consumer = null;
Session sessionToClose = null;
Connection conToClose = null;
boolean startConnection = false;
/**
* 接收消息
* @param session
* @param consumer
* @return
* @throws JMSException
*/
protected Message doReceive(Session session, MessageConsumer consumer) throws JMSException {
try {
this.consumer = consumer;
// Use transaction timeout (if available).
long timeout = getReceiveTimeout();
JmsResourceHolder resourceHolder =
(JmsResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory());
if (resourceHolder != null && resourceHolder.hasTimeout()) {
timeout = Math.min(timeout, resourceHolder.getTimeToLiveInMillis());
}
Message message = doReceive(consumer, timeout);
if (session.getTransacted()) {
// Commit necessary - but avoid commit call within a JTA transaction.
// 如果开启了jta事物,那么不会进行提交,jta事物会直接覆盖掉session事物
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this template -> commit.
JmsUtils.commitIfNecessary(session);
}
}
//autoAcknowledge如果为真,不进行自动确认
else if (isClientAcknowledge(session) && !autoAcknowledge) {
// Manually acknowledge message, if any.
if (message != null) {
message.acknowledge();
}
}
return message;
}
finally {
consumer = null;
}
}
/**
* 自定义的消息确认,关闭consumer和sesseionToClose是父类本身就要执行的,这里直接拷贝下来,能不改的地方尽量不改
* 该子类只是为了自定义确认消息
* @param message
* @throws JMSException
*/
public void msgAckAndcloseSession(Message message) throws JMSException {
message.acknowledge();
JmsUtils.closeMessageConsumer(consumer);
JmsUtils.closeSession(sessionToClose);
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);
}
/**
* 由于上面的doReceive(Session session, MessageConsumer consumer)需要调用这个方法,
* 而在父类里面这个方法是私有的,所以直接拷贝下来了
* @param consumer
* @param timeout
* @return
* @throws JMSException
*/
private Message doReceive(MessageConsumer consumer, long timeout) throws JMSException {
if (timeout == RECEIVE_TIMEOUT_NO_WAIT) {
return consumer.receiveNoWait();
}
else if (timeout > 0) {
return consumer.receive(timeout);
}
else {
return consumer.receive();
}
}
/**
* 该方法是为了防止确认消息前session被关闭,不然确认消息前session关闭会导致异常发生
* transactionalResourceFactory在父类中是私有且不可修改,因为只有这一个方法用到了transactionalResourceFactory
* 所以直接将JmsTemplateResourceFactory拷贝下来使用
* @param action
* @param startConnection
* @param <T>
* @return
* @throws JmsException
*/
public <T> T execute(SessionCallback<T> action, boolean startConnection) throws JmsException {
Assert.notNull(action, "Callback object must not be null");
this.startConnection = startConnection;
try {
Session sessionToUse = ConnectionFactoryUtils.doGetTransactionalSession(
getConnectionFactory(), transactionalResourceFactory, startConnection);
if (sessionToUse == null) {
conToClose = createConnection();
sessionToClose = createSession(conToClose);
if (startConnection) {
conToClose.start();
}
sessionToUse = sessionToClose;
}
if (logger.isDebugEnabled()) {
logger.debug("Executing callback on JMS Session: " + sessionToUse);
}
return action.doInJms(sessionToUse);
}
catch (JMSException ex) {
throw convertJmsAccessException(ex);
}
finally {
sessionToClose = null;
conToClose = null;
startConnection = false;
}
}
/**
* Sets new 是否开启手动确认标记.
*
* @param autoAcknowledge New value of 是否开启手动确认标记.
*/
public void setAutoAcknowledge(Boolean autoAcknowledge) {
this.autoAcknowledge = autoAcknowledge;
}
/**
* Gets 是否开启手动确认标记.
*
* @return Value of 是否开启手动确认标记.
*/
public Boolean getAutoAcknowledge() {
return autoAcknowledge;
}
/**
* 直接拷贝下来的
*/
private class JmsTemplateResourceFactory implements ConnectionFactoryUtils.ResourceFactory {
@Override
public Connection getConnection(JmsResourceHolder holder) {
return SimpleJmsTemplate.this.getConnection(holder);
}
@Override
public Session getSession(JmsResourceHolder holder) {
return SimpleJmsTemplate.this.getSession(holder);
}
@Override
public Connection createConnection() throws JMSException {
return SimpleJmsTemplate.this.createConnection();
}
@Override
public Session createSession(Connection con) throws JMSException {
return SimpleJmsTemplate.this.createSession(con);
}
@Override
public boolean isSynchedLocalTransactionAllowed() {
return SimpleJmsTemplate.this.isSessionTransacted();
}
}
}
6:第二个receiver测试类
package com.test.spring;
import com.system.freemwork.amq.SimpleJmsTemplate;
import org.apache.activemq.command.ActiveMQQueue;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.JMSException;
import javax.jms.TextMessage;
/**
* 类描述:receiver测试类
*
* @author fengyong
* @version 1.0
* @since 1.0
* Created by fengyong on 16/8/8 下午5:28.
*/
public class ActiveMqReceiver extends BaseTest{
@Autowired
private SimpleJmsTemplate receiverJmsTemplate;
@Autowired
private ActiveMQQueue activeMQQueue;
@Test
public void recerver() throws JMSException {
int i=1;
while (true){
i++;
TextMessage message = (TextMessage)receiverJmsTemplate.receive(activeMQQueue);
if (null != message) {
System.out.println("收到消息==================" + message.getText());
if(i==4){
throw new RuntimeException("Exception");
}
receiverJmsTemplate.msgAckAndcloseSession(message);
} else {
System.out.print("超时10秒");
break;
}
}
}
}
7:测试结果
收到消息==================我是第1个
收到消息==================我是第2个
收到消息==================我是第3个
java.lang.RuntimeException
: Exception
at com.test.spring.ActiveMqReceiver.show(ActiveMqReceiver.java:43)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)