關於ActiveMQ
Apache ActiveMQ是Apache軟件基金會所研發的開放源代碼消息中間件;由於ActiveMQ是一個純Java程序,因此只需要操作系統支持Java虛擬機,ActiveMQ便可執行。
雖然ActiveMQ的響應速度和體量比不上其他中間件,但是它體量小,易安裝,易使用。畢竟是老東家Apache出品,所以其他平臺和模塊對他支持也很友好。
如果用傳統的spring框架去集成ActiveMQ可能需要通過XML配置很多的bean,包括連接對象JMSConnectionFactory、Topic主題、Queue隊列、Producer生產者、Consumer消費者。
雖然這方式有助於去理解JMS的流程和類關係。
Spring-Boot集成ActiveMQ
在Spring-Boot有更簡單的集成方式。可以通過config類去配置,也可以通過yml配置。當然我推薦將連接信息配置在application.yml 或 application.properties來去配置,便於調整中間件的連接。
1.準備ActiveMQ 的服務
如果本地測試話,可以開一臺linux虛擬機,安裝ActiveMQ 的服務。安裝ActiveMQ 之前先要安裝和配置jdk的環境。然後纔可以部署ActiveMQ 的服務。
ActiveMQ 5.14版的tar包下載地址
https://archive.apache.org/dist/activemq/5.14.0/apache-activemq-5.14.0-bin.tar.gz
wget https://archive.apache.org/dist/activemq/5.14.0/apache-activemq-5.14.0-bin.tar.gz
下載成功後解壓
tar -zxvf apache-activemq-5.14.0-bin.tar.gz
進入解壓後的目錄,在進入bin/ 目錄下,執行
./activemq start
看到進程號了證明啓動成功了
pidfile created : '/opt/ActiveMQ/apache-activemq-5.14.0//data/activemq.pid' (pid '2867')
也可以使用
netstat -tnlp
查看一下端口開放情況
此時ActiveMQ 的服務已經啓動成功,記得添加虛擬機的端口映射。然後訪問ActiveMQ 的管理畫面
admin畫面的端口是8161
tcp協議端口是 61616
初始賬號 : admin
初始密碼: admin
2.Spring-Boot必要依賴
可以新建一個工程spring-boot工程天機ActiveMq的依賴和JMS的依賴,不建議直接使用
spring-boot-starter-activemq
這樣,他會把mq服務集成到項目中,且只會開放61616端口,沒有管理畫面。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dl.demo</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
-->
<!--ActiveMQ依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
</dependency>
<!--JMS-->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
切記JMS的包不能少,不會啓動報錯,連接不上mq服務。
3.Spring-Boot 中ActiveMQ連接配置
我使用的yml文件配置
# activemq config
spring.activemq:
broker-url: tcp://localhost:61616?wireFormat.maxInactivityDuration=0
user: admin
password: admin
in-memory: false
pool.enabled: false
爲了演示,我只加入了最基本的配置
4.代碼實裝
實裝代碼前要明確中間件中一些專業名詞
發佈者訂閱者模式
Topic 主題 (一對多)
Queue 隊列 (點對點)
生產者消費者模式
Queue 模式(點對點)
Topic模式 (一對多)
第一步需要使用 @EnableJms 開啓spring-boot對JMS的支持
第二步爲了方便後續對Topic和Queue的操作,定義了初始的隊列和主題
第三步爲了實現Topic和Queue共存,需要定義兩個消息容器 JmsListenerContainerFactory
package com.dl.demo.actvicemq;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* ActiveMQ配置類
*/
@Configuration
@EnableJms
public class ActiveMqConfig {
/**
* mq連接對象
*/
@Autowired
private ConnectionFactory connectionFactory;
/**
* 初始化隊列配置
*
* @return
*/
@Bean
public Queue floatQueue() {
return new ActiveMQQueue(DestinationConstant.FLOAT_01_QUEUE);
}
/**
* 初始化主題配置
*
* @return
*/
@Bean
public Topic startTopic() {
return new ActiveMQTopic(DestinationConstant.SERVER_START_TOPIC);
}
/**
* 主題消息容器配置
*
* @return
*/
@Bean
public JmsListenerContainerFactory<?> topicListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
factory.setConnectionFactory(connectionFactory);
return factory;
}
/**
* 隊列消息容器配置
*
* @return
*/
@Bean
public DefaultJmsListenerContainerFactory queueListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// 設置爲queue方式目標
factory.setPubSubDomain(false);
factory.setConnectionFactory(connectionFactory);
return factory;
}
}
一切準備就緒開始定義生產者和消費者
生產者定義
package com.dl.demo.actvicemq;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* ActiveMQ消費者
*/
@Component
public class ActiveMqProducer implements CommandLineRunner {
/**
* log
*/
private static final Logger logger = LoggerFactory.getLogger(ActiveMqProducer.class);
/**
* 隊列
*/
@Autowired
private Queue queue;
/**
* 主題
*/
@Autowired
private Topic topic;
/**
* JMS發送模板
*/
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 啓動聲明
*
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
logger.info(this.getClass().getName() + " is running.");
}
/**
* 通過主題發送mq
*
* @param topicName
* @param message
*/
public void sendMessageByTopic(String topicName, String message) {
logger.info("sendMessageByTopic-- topic:"+topicName+", message:"+message);
this.setTopic(topicName);
jmsMessagingTemplate.convertAndSend(topic, message);
}
/**
* 通過對列發送mq
*
* @param queueName
* @param message
*/
public void sendMessageByQueue(String queueName, String message) {
logger.info("sendMessageByTopic-- queue:"+queueName+", message:"+message);
this.setQueue(queueName);
jmsMessagingTemplate.convertAndSend(queue, message);
}
/**
* 主題設定
*
* @param topicName
*/
private void setTopic(String topicName) {
this.topic = new ActiveMQTopic(topicName);
}
/**
* 主題設定
*
* @param queueName
*/
private void setQueue(String queueName) {
this.queue = new ActiveMQQueue(queueName);
}
}
我特地將這個類實現了CommandLineRunne接口,便於在實例化的時候能看到啓動信息。
注意這裏發送mq的核心對象 JmsMessagingTemplate ,直接注入就可以使用。
在這個類中定義兩個外部可以調用的方法,sendMessageByTopic、sendMessageByQueue
只需要傳入主題名或者隊列名和message。
定義消費者
package com.dl.demo.actvicemq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* ActiveMQ消費者
*/
@Component
public class ActiveMqConsumer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(ActiveMqConsumer.class);
/**
* 流程01 float_01 隊列消息監聽
*
* @param message 消息
*/
@JmsListener(destination = DestinationConstant.FLOAT_01_QUEUE,
containerFactory = "queueListenerContainerFactory")
public void receiveQueueFloat01(String message) {
logger.info("receiveQueueFloat01-- " + DestinationConstant.FLOAT_01_QUEUE +
" receive queue msg:" + message);
}
/**
* 服務啓動主題 server_start 主題消息監聽
*
* @param message
*/
@JmsListener(destination = DestinationConstant.SERVER_START_TOPIC,
containerFactory = "topicListenerContainerFactory")
public void receiveTopicServerStart(String message) {
logger.info("receiveTopicServerStart-- " + DestinationConstant.SERVER_START_TOPIC +
" receive topic msg:" + message);
}
/**
* 啓動聲明
*
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
logger.info(this.getClass().getName() + " is running. ");
}
}
@JmsListener 接口註解中屬性destination表示監聽的主題或隊列名
因爲IOC容器裏面我注入兩個個消息容器,在使用@JmsListener需要注意使用containerFactory 屬性標註消息容器的對象名。否則默認配置,可能回到隊列或主題的消息收不到。
4.代碼測試
我對外提供了一個api接口
package com.dl.demo.controller;
import com.dl.demo.actvicemq.ActiveMqProducer;
import com.dl.demo.common.BaseResponse;
import com.dl.demo.controller.entity.MqSendRequest;
import com.dl.demo.service.MqOperationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@RestController
public class MqSendController {
@Autowired
private MqOperationService mqOperationService;
@PostMapping("/sendmq")
public BaseResponse sendMq(@Valid @RequestBody MqSendRequest request, BindingResult result, HttpServletResponse httpResponse){
BaseResponse response = new BaseResponse();
if(!result.hasErrors()){
try {
mqOperationService.sendMq(request);
response.setHttpStatus("200");
response.setMessage("request is ok");
} catch (Exception e){
httpResponse.setStatus(500);
response.setHttpStatus("500");
response.setMessage("mq send failed");
}
} else {
httpResponse.setStatus(401);
response.setHttpStatus("401");
response.setMessage("request formatter is wrong");
}
return response;
}
}
service實現
package com.dl.demo.service;
import com.dl.demo.actvicemq.ActiveMqProducer;
import com.dl.demo.controller.entity.MqSendRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* mq操作用serviceImpl
*/
@Service
public class MqOperationServiceImpl implements MqOperationService {
@Autowired
private ActiveMqProducer producer;
@Override
public void sendMq(MqSendRequest request) throws Exception {
if(request.getMqType().equals("topic")||request.getMqType().equals("Topic")){
producer.sendMessageByTopic(request.getToName(),request.getMessage());
} else if(request.getMqType().equals("queue")||request.getMqType().equals("Queue")) {
producer.sendMessageByQueue(request.getToName(),request.getMessage());
} else {
throw new Exception("mq Type is wrong");
}
}
}
api測試 topic模式
後臺log確認
api測試 queue模式
後臺log確認
完成了,nice!!!!!