前言
前面一節,我們提到ActiveMQ是使用Java編寫,並實現了JMS(Java Message Service)的消息中間件,那麼如何使用Java來操作ActiveMQ呢?
本節我們會通過三種不同的方式來整合ActiveMQ。
普通方式整合ActiveMQ
首先,創建一個Maven項目並選擇依賴的jar包
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
</dependencies>
編寫隊列Queue的消息收發
這裏需要提一提ActiveMQ中的三種簽收模式
簽收模式 | 解釋 |
---|---|
AUTO_ACK(Session.AUTO_ACKNOWLEDGE) | 只要客戶端拿到消息,就代表它消費成功了,mq 服務器也會刪除該消息 |
CLIENT_ACK(Session.CLIENT_ACKNOWLEDGE) | 客戶端要拿到消息,而且必須手動確認,該消費消費成功了,mq 服務器纔會刪除它 |
DUPS_ACK(批量的延時簽收) | 消息可重複確認,意思是此模式下,可能會出現重複消息,並不是一條消息需要發送多次ACK才行。 |
補充:DUPS_ACK
是一種潛在的AUTO_ACK
確認機制,爲批量確認而生,而且具有“延遲”確認的特點。對於開發者而言。DUPS_ACK
模式下的代碼結構和AUTO_ACKNOWLEDGE
一樣,不需要像CLIENT_ACKNOWLEDGE
那樣調用acknowledge()
方法來確認消息。
除此之外ActiveMQ還有兩種會話模式,一種是開啓事務支持,另外一種是簽收模式,由於事務模式非常耗費性能,因此我們一般使用簽收模式代替事務,注意簽收模式只在消息的消費階段起作用,此模式的性能非常高,使用客戶端簽收,安全性比較好。這個案例中我們使用的是事務模式。
public class TestMQQueue {
public static void main(String[] args) {
receiveFromMQ();
}
public static void receiveFromMQ() {
String brokerURL = "tcp://ip:61616";
// 1 創建連接工廠
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
// 2 建立連接
try {
Connection connection = activeMQConnectionFactory.createConnection("admin", "admin");
// 在監聽消息時,需要手動的啓動
connection.start();
// 3 創建會話對象
/**
* transacted : 是否支持事務
* acknowledgeMode:簽收模式,僅僅在事務爲false 才起作用,若是true,則不起作用
* 再簽收模式下,消費端必須使用message.acknowledge()之後,消息纔會真的被消費
*/
Session session = connection.createSession(true, 0);
// 5 消息發送的目的地(隊列)
Destination destination = new ActiveMQQueue("marco.queue");
// 6 消息的消費者
MessageConsumer messageConsumer = session.createConsumer(destination);
System.out.println("Keep Listening...");
messageConsumer.setMessageListener(new MessageListener() { // 新啓動子線程
@Override
public void onMessage(Message message) {
System.out.println("Receiving New Message!");
/**
* 消息類型
* message : 消息,具體有下面幾個重要的類型
* 1 文本消息類型 ActiveMQTextMessage
* 2 對象消息類型:ActiveMQObjectMessage
* 3 字節消息類型 ActiveMQBytesMessage
*/
ActiveMQTextMessage textMsg = (ActiveMQTextMessage) (message);
try {
String text = textMsg.getText(); // 獲取消息的內容
System.out.println("Message is:"+text);
session.commit(); // 消息被消費了,也要提交事務
System.out.println("Message has send successfully!");
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.in.read(); // 掛起主線程
} catch (Exception e) {
e.printStackTrace();
}finally {
//關閉資源
try {
consumer.close();
connection.close();
} catch (JMSException e) {
consumer = null;
connection = null;
e.printStackTrace();
}
}
}
public static void sentToMQ() {
String brokerURL = "tcp://www.yanli.ltd:61616";
// 1 連接工廠
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
// 2 連接
try {
Connection connection = activeMQConnectionFactory.createConnection("admin", "admin");
// 3 會話
/**
* transacted : 是否支持事務
* acknowledgeMode:簽收模式,僅僅在事務爲false 才起作用,若是true,他沒有作用
*/
Session session = connection.createSession(true, 0);
// 5 消息發送的目的地(隊列)
Destination destination = new ActiveMQQueue("java.queue");
// 4 消息的生產者
MessageProducer messageProducer = session.createProducer(destination);
// 6 使用生產者往mq 服務器裏面發消息
/**
* 消息類型
* message : 消息,具體有下面幾個重要的類型
* 1 文本消息類型 ActiveMQTextMessage
* 2 對象消息類型:ActiveMQObjectMessage
* 3 字節消息類型 ActiveMQBytesMessage
*/
// 創建文本消息
ActiveMQTextMessage activeMQTextMessage = new ActiveMQTextMessage();
activeMQTextMessage.setText("{\"id\":1}"); //{"id":1}
// 7 發送消息
messageProducer.send(activeMQTextMessage);
// 8 在事務模型裏面,需要提交事務
session.commit();
System.out.println("發送成功");
} catch (JMSException e) {
e.printStackTrace();
}finally {
// 關閉資源
}
}
}
注意,如果使用事務模式的話,必須要使用session.commit()
提交事務,否則增加或者修改等操作不會生效。
編寫主題Topic的消息收發
由於事務模式的性能不是特別好,因此,建議使用下面這個案例的簽收模式,注意,簽收模式下必須要使用message.acknowledge()
,表示消息已經被簽收,否則,就算消息被 “消費”,隊列裏的消息也不會被刪除。
public class TestMQTopic {
protected static final Message ActiveMQMessage = null;
public static void main(String[] args) {
// sentToMQ();
receiveFromMQ();
}
public static void sentToMQ() {
String brokerURL = "tcp://ip:61616";
// 1 創建連接工廠
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = null;
Session session = null;
MessageProducer producer = null;
try {
// 2 創建連接對象
connection = factory.createConnection();
connection.start();
// 3 創建會話對象
/**
* transacted : 是否支持事務
* acknowledgeMode:簽收模式,僅僅在事務爲false 才起作用,若是true,他沒有作用
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 5 消息發送的目的地(Topic主題)
Destination destination = new ActiveMQTopic("marco:topic");
producer = session.createProducer(destination);
ActiveMQTextMessage message = new ActiveMQTextMessage();
message.setText("hello");
producer.send(message);
System.out.println("Message has send successfully!");
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
producer.close();
session.close();
connection.close();
} catch (JMSException e) {
producer = null;
session = null;
connection = null;
e.printStackTrace();
}
}
}
public static void receiveFromMQ() {
String brokerURL = "tcp://ip:61616";
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
Connection connection = null;
MessageConsumer consumer = null;
Session session = null;
try {
connection = factory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
Destination destination = new ActiveMQTopic("marco:topic");
consumer = session.createConsumer(destination);
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
System.out.println("Receiving New Message!");
ActiveMQTextMessage textMessage = (ActiveMQTextMessage) message;
try {
System.out.println("Message is: " + textMessage.getText());
textMessage.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.out.println("Keep Listening...");
System.in.read();
} catch (JMSException | IOException e) {
e.printStackTrace();
} finally {
try {
consumer.close();
connection.close();
session.close();
} catch (JMSException e) {
consumer = null;
connection = null;
session = null;
e.printStackTrace();
}
}
}
}
Spring整合ActiveMQ
還是新建Maven項目,並添加依賴,注意此次Spring整合ActiveMQ我們需要多添加一個spring-jms
依賴,spring-jms
在Spring裏面簡化對ActiveMQ的操作,對ActiveMQ的Java操作做了封裝,另外再導入一個spring-context
,那麼Spring的核心jar都會被依賴進來。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-all -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
接下來我們單獨創建一個common.properties
,用於存儲連接ActiveMQ的baseURL信息
mq.url=tcp://ip:61616
其次是消息提供者的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:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
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">
<context:property-placeholder location="common.properties"/>
<!-- 連接工廠 -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<constructor-arg name="brokerURL" value="${mq.url}"></constructor-arg>
</bean>
<!-- 使用連接池不用多次創建connection連接對象,可以將connection緩存 -->
<bean class="org.springframework.jms.connection.CachingConnectionFactory">
<property name="targetConnectionFactory" ref="connectionFactory"></property>
<!-- 緩存數量設置爲10個 -->
<property name="sessionCacheSize" value="10"></property>
</bean>
<!-- 在spring 裏面,通過JmsTemplate 來操作Activemq -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="defaultDestinationName" value="marco.topic"></property>
<!-- true:主題模式
false:隊列模式 -->
<property name="pubSubDomain" value="true"></property>
</bean>
</beans>
最後配置消費者的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:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd
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">
<context:property-placeholder location="common.properties"/>
<!-- 連接工廠 -->
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<constructor-arg name="brokerURL" value="${mq.url}"></constructor-arg>
</bean>
<bean id="bootQueueListener" class="com.marco.listener.BootQueueListener"></bean>
<!-- 設置監聽器 -->
<jms:listener-container connection-factory="connectionFactory" destination-type="topic">
<jms:listener destination="marco:topic" ref="bootQueueListener"/>
</jms:listener-container>
</beans>
這裏我們自定了一個監聽器,叫做BootQueueListener
,該類實現了MessageListener
的接口,實現對生產者生產的消息的監聽,一旦有消息進入隊列,那麼BootQueueListener
就可以監聽到消息並獲取消息內容,但需要注意的是,我在配置文件中配置的是pubSubDomain=true
,因此使用的是主題模式。必須建立連接之後才能測試。
package com.marco.listener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.activemq.command.ActiveMQTextMessage;
public class BootQueueListener implements MessageListener{
@Override
public void onMessage(Message message) {
System.out.println("Receiving New Message!");
ActiveMQTextMessage textMessage = (ActiveMQTextMessage)message;
try {
// 獲取消息的內容
System.out.println("Message is: " + textMessage.getText());
textMessage.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
因此我這裏就使用ActiveMQ的Web頁面來發送請求,充當消息生產者,測試監聽器的監聽效果。另外需要注意的是監聽器的線程要保持不中斷的狀態,因此我使用System.in.read()
使主線程處於保持狀態。
public class ActiveMQApp {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"classpath:spring-jms-consumer.xml");
/////////////////////////////// 發送消息///////////////////////
// JmsTemplate jsmTemplate = context.getBean(JmsTemplate.class);
// jsmTemplate.convertAndSend("how are you?");
/////////////////// 監聽消息//////////////////////////
try {
System.out.println("開始監聽");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
啓動應用程序之後,開始監聽消息
接着進入ActiveMQ的Web可視化頁面,編輯消息內容,併發送,因爲我啓用的是Topic
模式,所以在Queue or Topic
一欄一定要選擇Topic
我們再來查看控制檯,發現剛剛我們發送的消息順利被接收了!測試成功!
SpringBoot整合ActiveMQ
當然了,以上方式在實際開發中並不會用到… 而會使用SpringBoot來整合ActiveMQ,怎麼簡單怎麼來嘛…
第一步:創建SpringBoot項目並修改配置文件
首先創建一個SpringBoot項目,導入Spring Web和ActiveMQ的啓動裝置(spring-boot-starter-activemq
)
pom.xml
文件內容如下
<?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.1.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.marco</groupId>
<artifactId>springboot-activemq</artifactId>
<version>1.0</version>
<name>springboot-activemq</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
接着創建application.yml
並添加以下內容
spring:
activemq:
# ip爲服務器的ip地址,若是本機,則設置爲127.0.0.1
broker-url: tcp://ip:61616
jms:
template:
# 默認的隊列名稱
default-destination: marco:queue
# pub-sub-domain: true 爲true表示使用topic主題模式,默認爲false,使用隊列模式
# listener:
# acknowledge-mode: client
第二步:創建生產者
package com.marco;
import java.util.UUID;
import javax.jms.JMSException;
import javax.jms.Message;
import org.apache.activemq.command.ActiveMQMessage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessagePostProcessor;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootActivemqApplicationTests {
@Autowired
private JmsTemplate jmsTemplate;
@Test
public void contextLoads() {
String msg = "hello mq!";
jmsTemplate.convertAndSend(msg);
System.out.println("Message has send successfully!");
}
}
第三步:創建消費者
package com.marco.listener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.activemq.command.ActiveMQTextMessage;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.marco.BloomFilter;
@Component
public class BootQueueListener implements MessageListener{
@Override
@JmsListener(destination="marco:queue")
public void onMessage(Message message) {
System.out.println("Receiving New Message!");
ActiveMQTextMessage textMessage = (ActiveMQTextMessage)message;
try {
// 獲取消息的內容
System.out.println("Message is: " + textMessage.getText());
// 表示已經收到消息了
textMessage.acknowledge();
} catch (JMSException e) {
e.printStackTrace();
}
}
}