一、引入jar包
需要引入
activemq-all包
spring-jms包
在项目的父pom文件,引入:
<!-- activemq依赖包 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
如果日志框架为logback时,将activemq-all替换为分别引入activemq组件,原因为与logback有jar包冲突
见:https://blog.csdn.net/csj50/article/details/99636261
<!-- activemq依赖包 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-client</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-spring</artifactId>
<version>5.15.9</version>
</dependency>
二、建立spring-jms.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core.xsd">
<!-- JSM服务商提供的连接工厂 -->
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<!-- broker地址 -->
<property name="brokerURL" value="${broker_url}" />
<!-- 强制使用异步发送 -->
<property name="useAsyncSend" value="true" />
<!-- 是否允许send方法复制JMS Message对象 -->
<property name="copyMessageOnSend" value="false" />
<!-- 优化ACK选项 -->
<property name="optimizeAcknowledge" value="true" />
</bean>
<!-- spring提供带缓存功能的连接工厂 -->
<bean id="jmsFactoryPool"
class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标connect factory -->
<property name="targetConnectionFactory" ref="jmsConnectionFactory" />
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="10" />
</bean>
<!-- Spring JMS Template -->
<!-- 定义JmsTemplate的Queue类型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<constructor-arg ref="jmsFactoryPool" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定义JmsTemplate的Topic类型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<constructor-arg ref="jmsFactoryPool" />
<!-- pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="true" />
</bean>
<!-- 以下定义Queue,Topic,消费者 -->
<!-- 定义Queue -->
<bean id="helloWorldQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 设置消息队列的名字 -->
<constructor-arg index="0" value="hello.world.async.handle.queue?consumer.prefetchSize=10"/>
</bean>
<!-- 定义Topic -->
<bean id="whoAreYouTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="who.are.you.handle.topic?consumer.prefetchSize=10"/>
</bean>
<!-- 定义消费者 -->
<!-- 消息转换器 -->
<bean id="simpleMessageConverter" class="org.springframework.jms.support.converter.SimpleMessageConverter" />
<!-- 父消息监听器(公共参数) -->
<bean id="parentMessageListener" class="com.study.base.jms.AbstractJmsReceiveTemplate"
scope="prototype" abstract="true">
<!-- onMessage委托给execute -->
<property name="defaultListenerMethod" value="onReceive" />
<property name="messageConverter" ref="simpleMessageConverter" />
</bean>
<!-- 父消息监听容器(公共参数) -->
<bean id="parentListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"
abstract="true">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<!-- 初始x个Consumer, 可动态扩展到y -->
<property name="concurrentConsumers" value="4" />
<property name="maxConcurrentConsumers" value="4" />
<!-- 设置消息确认模式为Client -->
<property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
</bean>
<!-- 队列异步处理类1 -->
<bean id="queueAsyHandler1" class="com.study.base.handler.queueAsyHandler1"
parent="parentMessageListener"/>
<!-- 队列MQ监听容器1 -->
<bean id="queueContainer1" parent="parentListenerContainer">
<property name="destination" ref="helloWorldQueue"/>
<property name="messageListener" ref="queueAsyHandler1"/>
</bean>
<!-- 主题异步处理类1 -->
<bean id="topicAsyHandler1" class="com.study.base.handler.topicAsyHandler1"
parent="parentMessageListener"/>
<!-- 主题异步处理类2 -->
<bean id="topicAsyHandler2" class="com.study.base.handler.topicAsyHandler2"
parent="parentMessageListener"/>
<!-- 主题MQ监听容器1 -->
<bean id="topicContainer1" parent="parentListenerContainer">
<property name="destination" ref="whoAreYouTopic"/>
<property name="messageListener" ref="topicAsyHandler1"/>
</bean>
<!-- 主题MQ监听容器2 -->
<bean id="topicContainer2" parent="parentListenerContainer">
<property name="destination" ref="whoAreYouTopic"/>
<property name="messageListener" ref="topicAsyHandler2"/>
</bean>
</beans>
applicationContext.xml添加引用“spring-jms.xml”
<!-- 下面可以导入其他spring配置文件 -->
<import resource="classpath:/spring-env.xml" />
<import resource="classpath:/spring-datasource.xml"/>
<import resource="classpath:/spring-transaction.xml"/>
<import resource="classpath:/spring-jms.xml"/>
说明:
1)ActiveMQConnectionFactory是一个管理对象,用于创建连接。此类还实现QueueConnectionFactory和TopicConnectionFactory。您可以使用此连接创建QueueConnections和TopicConnections。
它有四个构造方法:
ActiveMQConnectionFactory();
ActiveMQConnectionFactory(String brokerURL);
ActiveMQConnectionFactory(String userName, String password, String brokerURL);
ActiveMQConnectionFactory(String userName, String password, URI brokerURL);
ActiveMQConnectionFactory(URI brokerURL);
2)brokerURL可选参数说明:http://activemq.apache.org/tcp-transport-reference.html
在配置文件中加入:
broker_url=tcp://localhost:61616?tcpNoDelay=true
3)ActiveMQConnectionFactory可选参数说明:http://activemq.apache.org/maven/apidocs/org/apache/activemq/ActiveMQConnectionFactory.html
4)CachingConnectionFactory可选参数说明:https://docs.spring.io/spring-framework/docs/2.5.x/api/org/springframework/jms/connection/CachingConnectionFactory.html
ActiveMQConnectionFactory不会复用connection、session、producer、consumer,每次连接都需要重新创建connection,再创建session,然后调用session的创建新的producer或者consumer的方法,然后用完之后依次关闭,比较浪费资源。
CachingConnectionFactory会缓存MessageProducer、MessageConsumer和Session。
5)spring提供的JmsTemplate可以方便的操作Queue类型和Topic类型
6)定义queue时,配置参数prefetchSize
prefetchSize是消费者预获取消息数量,重要的调优参数之一。broker将会批量push prefetchSize条消息给消费者。
控制消费者一次最多同时收到多少条消息,需配合optimizeAck使用。
7)consumer有推和拉2种方式获取消息:当prefetchSize = 0时,pull;当prefetchSize > 0时,push。
推是消费者被动的接收,拉是消费者主动的获取。activeMQ默认是推。
拉的方式:consumer.receive() 或 推的方式:consumer.setMessageListener(listener)
对于receive方式,如果prefetchSize = 0并且本地没有缓存消息,则发送一个pull命令给broker;否则,则从本地缓存中取消息。
8)consumer可选参数:http://activemq.apache.org/destination-options.html
9)JMS Template可以视为MQ的生产者
10)消息监听器分为
10-1)消息监听容器类:
10-1-1)SimpleMessageListenerContainer
10-1-2)DefaultMessageListenerContainer(使用它)
默认的消息监听容器类是DefaultMessageListenerContainer,DefaultMessageListenerContainer是一个用于异步消息监听的管理类
10-2)消息监听类:自己继承
10-2-1)MessageListener
10-2-2)SessionAwareMessageListener
10-2-3)MessageListenerAdapter(使用它)
11)消息转换类:可以不用显示指定,默认为SimpleMessageConverter
12)消息确认模式有:
* @see javax.jms.Session#AUTO_ACKNOWLEDGE
* @see javax.jms.Session#CLIENT_ACKNOWLEDGE
* @see javax.jms.Session#DUPS_OK_ACKNOWLEDGE
AUTO_ACKNOWLEDGE:自动应答,默认的应答方式
CLIENT_ACKNOWLEDGE:客户端应答,应答由应用程序在接收到消息后触发。DefaultMessageListenerContainer会对CLIENT_ACKNOWLEDGE应答模式自动确认
DUPS_OK_ACKNOWLEDGE:允许在收到多个消息之后一次完成确认
三、添加消费者相关代码
1、添加包com.study.base.jms
新增AbstractJmsReceiveTemplate.java(监听器封装类)
package com.study.base.jms;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.jms.listener.adapter.MessageListenerAdapter;
import com.study.base.common.Constants;
import com.study.base.util.SessionUtil;
public abstract class AbstractJmsReceiveTemplate extends MessageListenerAdapter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
public void onReceive(Object param) {
try {
//日志跟踪
MDC.put(Constants.SESSION_ID, SessionUtil.getSessionId());
String slog = "==recive param==" + param;
logger.info(slog);
receive(param);
} catch (Throwable e) {
logger.error("系统异常:{}", e);
} finally {
MDC.remove(Constants.SESSION_ID);
}
}
protected abstract void receive(Object param) throws Exception;
}
2、添加包com.study.base.sender
新增QueueSender.java(队列发送者)
package com.study.base.sender;
import javax.annotation.Resource;
import javax.jms.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
/**
* 队列发送者
* @author User
*
*/
@Component
public class QueueSender {
private final static Logger logger = LoggerFactory.getLogger(QueueSender.class);
@Resource(name = "helloWorldQueue")
private Queue helloWorldQueue;
@Autowired
JmsTemplate jmsQueueTemplate;
public void send(String message) {
logger.info("send queue msg: {}", message);
jmsQueueTemplate.convertAndSend(helloWorldQueue, message);
logger.info("send queue msg end...");
}
}
新增TopicSender.java(主题发布者)
package com.study.base.sender;
import javax.annotation.Resource;
import javax.jms.Topic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
/**
* 主题发布者
* @author User
*
*/
@Component
public class TopicSender {
private final static Logger logger = LoggerFactory.getLogger(QueueSender.class);
@Resource(name = "whoAreYouTopic")
private Topic whoAreYouTopic;
@Autowired
JmsTemplate jmsTopicTemplate;
public void send(String message) {
logger.info("send topic msg: {}", message);
jmsTopicTemplate.convertAndSend(whoAreYouTopic, message);
logger.info("msg send end...");
}
}
3、添加包com.study.base.handler
新增queueAsyHandler1.java(队列消费者1)
package com.study.base.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.study.base.jms.AbstractJmsReceiveTemplate;
public class queueAsyHandler1 extends AbstractJmsReceiveTemplate {
private final static Logger logger = LoggerFactory.getLogger(queueAsyHandler1.class);
@Override
protected void receive(Object param) throws Exception {
logger.info("queue handler1 msg receive begin...");
logger.info("param: {}", (String)param);
logger.info("queue handler1 msg receive end...");
Thread.currentThread().sleep(30*1000);
}
}
新增topicAsyHandler1.java(主题订阅者1)
package com.study.base.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.study.base.jms.AbstractJmsReceiveTemplate;
public class topicAsyHandler1 extends AbstractJmsReceiveTemplate {
private final static Logger logger = LoggerFactory.getLogger(topicAsyHandler1.class);
@Override
protected void receive(Object param) throws Exception {
logger.info("topic handler1 msg receive begin...");
logger.info("param: {}", (String)param);
logger.info("topic handler1 msg receive end...");
}
}
新增topicAsyHandler2.java(主题订阅者2)
package com.study.base.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.study.base.jms.AbstractJmsReceiveTemplate;
public class topicAsyHandler2 extends AbstractJmsReceiveTemplate {
private final static Logger logger = LoggerFactory.getLogger(topicAsyHandler2.class);
@Override
protected void receive(Object param) throws Exception {
logger.info("topic handler2 msg receive begin...");
logger.info("param: {}", (String)param);
logger.info("topic handler2 msg receive end...");
}
}
4、TestController新增测试方法:queueSend、topicSend
package com.study.web.resources;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.study.base.exception.BizException;
import com.study.base.exception.SysException;
import com.study.base.sender.QueueSender;
import com.study.base.sender.TopicSender;
@Controller
@RequestMapping("mvc")
public class TestController {
private static Logger log = LoggerFactory.getLogger(TestController.class);
@Autowired
QueueSender queueSender;
@Autowired
TopicSender topicSender;
@RequestMapping("hello")
private String hello() {
log.info("hello in...");
log.info("hello out...");
return "hello";
}
@RequestMapping("testError")
public void testError() {
log.info("testError in...");
log.info("testError out...");
throw new BizException("1234567890", "报错信息1");
}
@RequestMapping("testError2")
public void testError2() {
log.info("testError2 in...");
log.info("testError2 out...");
throw new SysException("0987654321", "报错信息2");
}
@RequestMapping("queueSend")
public void queueSend(HttpServletRequest request) {
String msg = request.getParameter("msg");
queueSender.send(msg);
}
@RequestMapping("topicSend")
public void topicSend(HttpServletRequest request) {
String msg = request.getParameter("msg");
topicSender.send(msg);
}
}
5、浏览器访问:
http://127.0.0.1:8080/webapp2/mvc/queueSend?msg=654321
向队列发送消息,日志查看消费者消费情况
日志信息:
INFO | send queue msg: 654321
INFO | send queue msg end...
INFO | ==recive param==654321
INFO | queue handler1 msg receive begin...
INFO | param: 654321
INFO | queue handler1 msg receive end...
http://127.0.0.1:8080/webapp2/mvc/topicSend?msg=123456
向主题发送消息,日志查看订阅者消费情况
日志信息:
INFO | send topic msg: 123456
INFO | msg send end...
INFO | ==recive param==123456
INFO | topic handler1 msg receive begin...
INFO | param: 123456
INFO | topic handler1 msg receive end...
INFO | ==recive param==123456
INFO | topic handler1 msg receive begin...
INFO | ==recive param==123456
INFO | ==recive param==123456
INFO | ==recive param==123456
INFO | topic handler1 msg receive begin...
INFO | param: 123456
INFO | topic handler1 msg receive end...
INFO | param: 123456
INFO | topic handler1 msg receive end...
INFO | ==recive param==123456
INFO | topic handler2 msg receive begin...
INFO | ==recive param==123456
INFO | ==recive param==123456
INFO | topic handler2 msg receive begin...
INFO | topic handler2 msg receive begin...
INFO | topic handler1 msg receive begin...
INFO | param: 123456
INFO | param: 123456
INFO | topic handler2 msg receive end...
INFO | topic handler2 msg receive begin...
INFO | param: 123456
INFO | param: 123456
INFO | topic handler2 msg receive end...
INFO | topic handler2 msg receive end...
INFO | param: 123456
INFO | topic handler2 msg receive end...
INFO | topic handler1 msg receive end...
四、观察消费情况
1、队列模式下,点一下,消费一次
2、发布订阅模式下,点一下,会收到8条消息。原因为定义了2个订阅者,但是父监听容器concurrentConsumers设置了4个,所以就收到8次了(需要注意,发布订阅模式下,消费者数量只能设为1,否则会有重复消费的问题)
参考资料:
ActiveMQ - prefetch与optimizeACK
http://www.voidcn.com/article/p-vxbpkfbp-boh.html
ActiveMQ 到底是推还是拉?
https://www.cnblogs.com/allenwas3/p/8574470.html
JMS消息的确认方式
https://www.cnblogs.com/chenying99/p/3164640.html
注:最新代码上传至https://github.com/csj50/webapp2.git
注2:ActiveMQ管理界面介绍https://blog.csdn.net/csj50/article/details/86556835