Java web项目创建笔记14 之《整合activemq》

一、引入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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值