activemq是一个非常常用的消息中间件,本节课进行SpringBoot2.0整合activemq队列模式的讲解,关于activemq基本介绍网上很多,这里就不进行详细讲解了。
SpringBoot关于activemq的官网:https://docs.spring.io/spring-boot/docs/2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-activemq,这里有对SpringBoot使用activemq详细讲解,下面直接上代码
一:队列模式:queue
1.加入activemq相关依赖
一个依赖是activemq的,另外一个是activemq的线程池,类似于jdbc连接池
<!-- 整合消息队列ActiveMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- 如果配置线程池则加入 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
2.编写配置文件
spring:
activemq:
broker-url: tcp://localhost:61616
user: admin
password: admin
#下列配置要增加依赖
pool.enabled: true
pool.max-connections: 100
3.启动类添加支持
4.创建一个队列交给Spring管理
在启动类中创建一个队列交给Spring管理,后续需要使用的时候直接依赖注入就行
package net.xdclass.base_project;
import org.apache.activemq.command.ActiveMQQueue;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import javax.jms.Queue;
@SpringBootApplication //一个注解顶下面3个
@MapperScan("net.xdclass.base_project.mapper")
@EnableJms//开启支持jms
public class XdclassApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(XdclassApplication.class, args);
}
@Bean//创建一个bean,交给Spring管理
public Queue queue(){
return new ActiveMQQueue("testqueue");//new一个名字叫testqueue的队列
}
}
5.消息生产者接口,两种发送方式
下面我们写消息生产者,这里有两种,先定义一个service:
package net.xdclass.base_project.service;
import javax.jms.Destination;
/**
* 消息生产者
*/
public interface ProducerService {
/**
* 指定消息队列,发送消息
* @param destination
* @param message
*/
public void sendMessage(Destination destination, final String message);
/**
* 使用已经配置好的消息队列发送消息
* @param message
*/
public void sendMessage(final String message);
}
实现类:
package net.xdclass.base_project.service.impl;
import net.xdclass.base_project.service.ProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import javax.jms.Destination;
import javax.jms.Queue;
@Service
public class ProducerServiceImpl implements ProducerService {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Queue queue;
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(Destination destination, String message) {
jmsTemplate.convertAndSend(destination,message);
}
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(String message) {
jmsTemplate.convertAndSend(this.queue,message);
}
}
这里有两个方法,这两个方法的主要区别就是一个是使用的配置好的destination,另外一个是对外暴露由外界传入的,在微服务项目或者分布式项目中,我们只写一个服务层,需要发消息给谁可能需要控制器来决定,所以消息的目的地可以由外界传入,我们也可以在本服务中定义好destination,使用的时候直接依赖注入就行,这里的queue就是刚刚在启动类中定义好的destination,直接在业务中用。
JmsMessagingTemplate就是一个模板工具,用来发送消息到broker的对象,他有很多api,如果需要使用其他的话可以去官网查看
6.控制器
这里写一个控制器模拟微信支付完成的回调函数,调用我们的接口,然后进行消息的发送,这里order接口我们创建了一个order.queue的destination
package net.xdclass.base_project.controller;
import javax.jms.Destination;
import net.xdclass.base_project.domain.JsonData;
import net.xdclass.base_project.service.ProducerService;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能描述:模拟微信支付回调
*
*/
@RestController
@RequestMapping("/api/v1")
public class OrderController {
@Autowired
private ProducerService producerService;
/**
* 功能描述:微信支付回调接口
* @param msg 支付信息
* @return
*/
@GetMapping("order")
public Object order(String msg){
Destination destination = new ActiveMQQueue("order.queue");
producerService.sendMessage(destination, msg);
return JsonData.buildSuccess();
}
@GetMapping("common")
public Object common(String msg){
producerService.sendMessage(msg);
return JsonData.buildSuccess();
}
}
7.测试是否发送成功
启动项目,访问http://localhost:8080/api/v1/order?msg=123
查看activemq控制台,我们发现已经生成一个order.queue,并且里面有一条未消费的消息
8.消费者
下面我们写消费者进行消息的消费:
package net.xdclass.base_project.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class OrderConsumer {
@JmsListener(destination = "order.queue")
public void receiveQueue(String text){
System.out.println("orderqueue的报文为:"+text);
}
}
@JmsListener(destination = "order.queue"),这个就是核心代码,表示监听对应名称的消息队列,队列(queue)的消息是实时的,持久化的,就算生产者发送的时候消费者不在线,当消费者上线的时候也会进行消费,下面进行测试,直接重启项目,运行发现有打印了,说明queue的消息是持久化的:
注意:一般queue在消费者启动的时候,正常情况下是没有消息堆积的,也就是没有待消费的消息 ,队列的消息是消费一条就消失一条,如果有等待消费的队列消息说明出问题了
二.发布订阅者模式topic
1.在刚刚的配置文件中加入配置文件,支持发布订阅模型,因为默认只支持点对点
server:
port: 8081
spring:
activemq:
broker-url: tcp://192.168.1.11:61616
user: admin
password: admin
#下列配置要增加依赖
pool.enabled: true
pool.max-connections: 100
#需要加入配置文件,支持发布订阅模型,默认只支持点对点
jms:
pub-sub-domain: true
2.在启动类中新增一个topic
与queue一样,在启动类中新增一个topic的bean,这样使用的时候直接注入就行了
@Bean
public Topic topic(){
return new ActiveMQTopic("video.topic");
}
3.新增消息生产者
Service:接着上面队列的消息生产这下面加入一个方法
package net.xdclass.base_project.service;
import javax.jms.Destination;
/**
* 功能描述:消息生产
*
* <p> 创建时间:May 3, 2018 9:48:30 PM </p>
*
*/
public interface ProducerService {
/**
* 功能描述:指定消息队列,还有消息
* @param destination
* @param message
*/
public void sendMessage(Destination destination, final String message);
/**
* 功能描述:使用默认消息队列, 发送消息
* @param message
*/
public void sendMessage( final String message);
/**
* 功能描述:消息发布者
* @param msg
*/
public void publish(String msg);
}
ServiceImpl:注入刚刚创建的topic,然后调用发送的方法:
package net.xdclass.base_project.service.impl;
import javax.jms.Destination;
import javax.jms.Queue;
import javax.jms.Topic;
import net.xdclass.base_project.service.ProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
/**
* 功能描述:消息生产者
*
* <p> 创建时间:May 2, 2018 11:29:47 PM </p>
*
*/
@Service
public class ProducerServiceImpl implements ProducerService{
@Autowired
private Queue queue;
@Autowired
private JmsMessagingTemplate jmsTemplate; //用来发送消息到broker的对象
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(Destination destination, String message) {
jmsTemplate.convertAndSend(destination, message);
}
//发送消息,destination是发送到的队列,message是待发送的消息
@Override
public void sendMessage(final String message) {
jmsTemplate.convertAndSend( message);
}
//=======发布订阅相关代码=========
@Autowired
private Topic topic;
@Override
public void publish(String msg) {
this.jmsTemplate.convertAndSend(this.topic, msg);
}
}
4.Controller调用生产者
下面在刚刚queue的控制器中写一个调用消息发布的接口:
/**
* 发布消息:生产者
* @param msg
* @return
*/
@GetMapping("common")
public Object common(String msg){
producerService.sendMessage(msg);
return JsonData.buildSuccess();
}
5.新建订阅者
新建一个订阅者,订阅刚刚定义的发布者
package net.xdclass.base_project.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class TopicSub {
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive1(String text){
System.out.println("video.topic 消费者:receive1="+text);
}
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive2(String text){
System.out.println("video.topic 消费者:receive2="+text);
}
@JmsListener(destination="video.topic", containerFactory="jmsListenerContainerTopic")
public void receive3(String text){
System.out.println("video.topic 消费者:receive3="+text);
}
}
6.测试
发现已经访问成功,并且成功订阅,发布订阅模式消息不会被消费掉,他会一直在mq上。
7.解决queue与topic兼顾问题
这时候我们再返回前面发送一个队列的消息:
我们发现这时候队列的消息成功发送了,但是并没有被消费,可是之前就是可以的啊,现在为什么不行了呢,这是因为springboot不同时支持queue与topic,只支持点对点也就是queue,刚刚我们在配置文件中开启了发布订阅者模式,则无法发送和接收queue消息,我们可以通过配置让他同时支持两种模式,在启动类中加入如下代码修改mq的工厂,让他支持两种模式:
/**
* 同时支持topic与queue
* @param activeMQConnectionFactory
* @return
*/
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}
在订阅者中加入如下属性
containerFactory = "jmsListenerContainerTopic"
@JmsListener(destination="video.topic",containerFactory = "jmsListenerContainerTopic")
public void receive1(String text){
System.out.println("video.topic 消费者:receive1="+text);
}
注意,这时候要把之前配置文件中加的注释掉
#需要加入配置文件,支持发布订阅模型,默认只支持点对点
# jms:
# pub-sub-domain: true
这样,就可以同时使用queue以及topic了
源码地址:https://gitee.com/xuxinsunqizheng/springboot_module.git