使用 gradle 搭建 spring + ActiveMQ 的一個 demo
參考:https://juejin.im/post/5ad46f34518825651d08265c#heading-15
https://www.imooc.com/learn/856
第一個是掘金的一篇文章,第二個是慕課網上的一個視頻課程。
以下是使用 gradle 搭建 spring + ActiveMQ 的一個 demo 的步驟
1.下載 ActiveMQ
首先去 ActiveMQ 官網下載 ActiveMQ 軟件,官網地址:https://activemq.apache.org/
2.引入依賴
創建一個新的 ActiveMQ 項目,並在 build.gradle 中引入相關的依賴
// https://mvnrepository.com/artifact/org.springframework/spring-context
compile group: 'org.springframework', name: 'spring-context', version: '5.1.9.RELEASE'
// https://mvnrepository.com/artifact/org.apache.activemq/activemq-all
compile group: 'org.apache.activemq', name: 'activemq-all', version: '5.15.9'
// https://mvnrepository.com/artifact/org.apache.activemq/activemq-pool
compile group: 'org.apache.activemq', name: 'activemq-pool', version: '5.15.9'
// https://mvnrepository.com/artifact/org.springframework/spring-jms
compile group: 'org.springframework', name: 'spring-jms', version: '5.1.9.RELEASE'
3.開始寫代碼
3.1 隊列模式
3.1.1 創建消息消費者
爲了觀察隊列模式的特點,這裏我創建了兩個隊列模式的消費者,其實是自定義兩個消息監聽器,讓其實現 MessageListener 接口,重寫 onMessage() 方法。代碼如下:
package com.caihao.activemqdemo.consumer;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* 隊列模式-消息消費者1
*/
public class QueueListener1 implements MessageListener{
@Override
public void onMessage(Message message) {
// 由於我這裏只負責接收TextMessage類型,因此做一個類型判斷
if (message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("隊列模式消費者1:"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
由於這兩個消息監聽器類代碼類似,因此就不放出另一個類了。
3.1.2 配置spring和activeMQ
在項目的 resources 資源環境目錄下新建一個 spring 的 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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--生產者和消費者都需要用到的-start-->
<!--配置包掃描路徑-->
<context:component-scan base-package="com.caihao.activemqdemo.producer"/>
<context:component-scan base-package="com.caihao.activemqdemo.consumer"/>
<!--ActiveMQ爲我們提供的ConnectionFactory-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<!--spring jms 爲我們提供的連接池,這個connectionFactory也是下面的jmsTemplate要使用的,
它其實就是對activeMQ的ConnectionFactory的一個封裝-->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!--提供一個隊列模式的目的地,點對點的-->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<!--目的地隊列的名字-->
<constructor-arg value="queue-demo"/>
</bean>
<!--生產者和消費者都需要用到的-end-->
<!--生產者所需要的-start-->
<!--配置jmsTemplate用於發送消息-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
<!--生產者所需要的-end-->
<!--消費者所需要的start-->
<!--配置消息監聽器1,即消息的消費者-->
<bean id="queueListener1" class="com.caihao.activemqdemo.consumer.QueueListener1"/>
<!--配置消息容器1-->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="queueDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="queueListener1"/>
</bean>
<!--配置消息監聽器2,即消息的消費者-->
<bean id="queueListener2" class="com.caihao.activemqdemo.consumer.QueueListener2"/>
<!--配置消息容器2-->
<bean id="jmsContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="queueDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="queueListener2"/>
</bean>
<!--消費者所需要的end-->
</beans>
具體的解釋已經寫在代碼中了,這裏說一下,由於我是打算用兩個隊列模式的消息監聽器,因此,在配置文件中我需要配置兩個消息監聽器和消息容器。其次就是,如果我們需要把生產者和消費者分開配置的話,則只需要按照我所註釋的生產者/消費者/公共的/start-end 進行劃分即可。
3.1.3 創建消息生產者
這裏,我們新建一個類叫 Producer,提供一個方法,在方法裏面通過 jmsTemplate 進行發送消息。其中裏面的 queueDestination 爲上面的 xml 配置文件中的 queueDestination,這裏我們不能使用 Autowired 註解,因爲他們的類型不一樣,我們需要通過 Resources 註解的 name 屬性來進行指定。
package com.caihao.activemqdemo.producer;
import javax.annotation.Resource;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
/**
* 消息生產者
*/
@Component
public class Producer {
@Autowired
private JmsTemplate jmsTemplate;
@Resource(name = "queueDestination")
private Destination queueDestination;
/**
* 發送隊列消息
*/
public void sendQueueMessage(String message) {
// 發送消息
jmsTemplate.send(queueDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
// 返回創建的消息
return session.createTextMessage(message);
}
});
}
}
3.1.4 運行測試代碼
生產者、消費者和配置文件都寫好之後就可以開始寫測試代碼了。這裏我們定義一個普通 java 類來運行代碼。在 main 方法中首先進行配置文件的加載,然後獲取生產者實例,接着調用生產者實例發送消息。
package com.caihao.activemqdemo;
import com.caihao.activemqdemo.producer.Producer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-activemq.xml");
// 獲取生產者實例
Producer producer = (Producer) ac.getBean("producer");
for (int i = 0; i < 100; i++) {
// 發送隊列消息
producer.sendQueueMessage("hello" + i);
}
// ac.close();
}
}
在運行測試代碼之前,還需要先將之前下載的 ActiveMQ 啓動起來。找到 ActiveMQ 解壓後的 bin 目錄,然後根據操作系統選擇 64 還是 32 位,然後運行 win64/win32 中的 activemq.bat 。當在啓動的窗口日誌中看到類似 http://0.0.0.0:8161/
的時候,說明 activemq 啓動起來了。這個時候,打開瀏覽器訪問 http://127.0.0.1:8161/
即可打開activemq的管理界面。然後點擊 Manager ActiveMQ broker
會彈出一個框讓輸入用戶名和密碼,activemq 的用戶名和密碼都是 admin 。之後運行測試代碼,就能在 activemq 的管理界面的導航欄中 Queues 一欄中看到消息隊列的相關信息了。控制檯也會打印出消費者所消費的信息,觀察兩個消費者所消費消息的規律,可以看到,它們基本上是一個消費者消費一條消息。一條消息被一個消費者消費了就不能再被另一個消費者所消費。另外,如果只啓動了消息的生產者進行發送消息,而消費者一個都沒啓動的話,消息生產者發送的消息會被存在消息隊列中,不會丟失,等到後面啓動了消費者之後,消費者還能自動去消息隊列裏面進行消費消息。
3.2 topic模式
廣播模式步驟和上面的隊列模式步驟類似。
3.2.1 創建消息消費者
在上面項目的基礎上,我再創建兩個消息的消費者,這次的消息消費者是 Topic 模式消費者,除了類名和隊列模式消費者不一樣,內容基本一致。代碼如下。
package com.caihao.activemqdemo.consumer;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* topic模式消費者1
*/
public class TopicListener1 implements MessageListener {
@Override
public void onMessage(Message message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("topic模式消費者1:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
第二個消費者類就不貼出了。
3.2.2 配置 spring 和 activemq
這裏我還是在之前新建的 spring-activemq.xml 中進行追加配置。首先配置 topic 模式的目的地。由於這個東西需要在生產者和消費者中都有用到,因此我將其放在生產者/消費者公共部分的 start/end 裏面。
<!--提供一個topic模式的目的地,廣播的-->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<!--topic模式名稱-->
<constructor-arg value="topic-demo"/>
</bean>
然後接着配置消息監聽器,這一部分,配置在消費者的 start/end 裏面就可以了。
<!--topic模式監聽器-->
<!--配置消息監聽器1,即消息的消費者-->
<bean id="topicListener1" class="com.caihao.activemqdemo.consumer.TopicListener1"/>
<!--配置消息容器1-->
<bean id="topicContainer1" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="topicDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="topicListener1"/>
</bean>
<!--配置消息監聽器2,即消息的消費者-->
<bean id="topicListener2" class="com.caihao.activemqdemo.consumer.TopicListener2"/>
<!--配置消息容器1-->
<bean id="topicContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="topicDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="topicListener2"/>
</bean>
這樣配置就完成了。爲了看的明白,我給出 spring-activemq.xml 的全部內容,並將 topic 模式追加的配置進行突出顯示。
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--生產者和消費者都需要用到的-start-->
<!--配置包掃描路徑-->
<context:component-scan base-package="com.caihao.activemqdemo.producer"/>
<context:component-scan base-package="com.caihao.activemqdemo.consumer"/>
<!--ActiveMQ爲我們提供的ConnectionFactory-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616"/>
</bean>
<!--spring jms 爲我們提供的連接池,這個connectionFactory也是下面的jmsTemplate要使用的,
它其實就是對activeMQ的ConnectionFactory的一個封裝-->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!--提供一個隊列模式的目的地,點對點的-->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<!--目的地隊列的名字-->
<constructor-arg value="queue-demo"/>
</bean>
<!--提供一個topic模式的目的地,廣播的-->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<!--topic模式名稱-->
<constructor-arg value="topic-demo"/>
</bean>
<!--生產者和消費者都需要用到的-end-->
<!--生產者所需要的-start-->
<!--配置jmsTemplate用於發送消息-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
<!--生產者所需要的-end-->
<!--消費者所需要的start-->
<!--配置消息監聽器1,即消息的消費者-->
<bean id="queueListener1" class="com.caihao.activemqdemo.consumer.QueueListener1"/>
<!--配置消息容器1-->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="queueDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="queueListener1"/>
</bean>
<!--配置消息監聽器2,即消息的消費者-->
<bean id="queueListener2" class="com.caihao.activemqdemo.consumer.QueueListener2"/>
<!--配置消息容器2-->
<bean id="jmsContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="queueDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="queueListener2"/>
</bean>
<!--topic模式監聽器-->
<!--配置消息監聽器1,即消息的消費者-->
<bean id="topicListener1" class="com.caihao.activemqdemo.consumer.TopicListener1"/>
<!--配置消息容器1-->
<bean id="topicContainer1" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="topicDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="topicListener1"/>
</bean>
<!--配置消息監聽器2,即消息的消費者-->
<bean id="topicListener2" class="com.caihao.activemqdemo.consumer.TopicListener2"/>
<!--配置消息容器1-->
<bean id="topicContainer2" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--指定連接工廠-->
<property name="connectionFactory" ref="connectionFactory"/>
<!--指定消息目的地-->
<property name="destination" ref="topicDestination"/>
<!--指定消息監聽器-->
<property name="messageListener" ref="topicListener2"/>
</bean>
<!--消費者所需要的end-->
</beans>
3.2.3 創建消息生產者
這裏還是在之前創建的 Producer 的類中追加一個方法發送 topic 模式的消息的方法。內容與 queue 隊列模式的差不多,首先引入一個 spring-activemq.xml 文件中定義的 topicDestination,然後通過 jmsTemplate 發送消息(注:這裏創建消息這裏,我使用了lambda表達式,實際內容其實和之前的創建消息代碼是一樣的)。
@Resource(name = "topicDestination")
private Destination topicDestination;
/**
* 發送topic模式消息
*
* @param message 消息
*/
public void sendTopicMessage(String message) {
// 發送消息
jmsTemplate.send(topicDestination, (session) -> session.createTextMessage(message));
}
3.2.4 運行測試代碼
這裏,直接修改之前運行隊列模式時的 main 方法,在發送消息的時候選擇調用sendTopicMessage()方法即可。在運行測試代碼之前不要忘了啓動 activemq 。通過運行測試代碼發現,兩個topic消費者都收到了同樣的消息,也就是一個消息被這兩個消費者同時消費了,就類似於廣播,每個監聽了該消息隊列的監聽者都能收到消息。在 activemq 的管理控制檯的 topic 界面上也能看到,Messages Enqueued 有100個,而 Message Dequeued 有200個。
topic 模式的特點就是類似於廣播,只要是對這個消息隊列有監聽的人都能收到消息。但是如果是先運行了消息生產者發佈消息,而沒有提前運行消費者監聽消息的話,那麼等消息消費者之後啓動監聽的話,是無法消費之前生產者發送的消息的。其實這也很好理解,就和廣播一樣,如果你沒有提前開啓廣播,那麼廣播之前的東西你是收聽不到的。
方法,在發送消息的時候選擇調用 sendTopicMessage() 方法即可。在運行測試代碼之前不要忘了啓動 activemq 。通過運行測試代碼發現,兩個topic 消費者都收到了同樣的消息,也就是一個消息被這兩個消費者同時消費了,就類似於廣播,每個監聽了該消息隊列的監聽者都能收到消息。在 activemq 的管理控制檯的 topic 界面上也能看到,Messages Enqueued 有 100 個,而Message Dequeued 有 200 個。
topic 模式的特點就是類似於廣播,只要是對這個消息隊列有監聽的人都能收到消息。但是如果是先運行了消息生產者發佈消息,而沒有提前運行消費者監聽消息的話,那麼等消息消費者之後啓動監聽的話,是無法消費之前生產者發送的消息的。其實這也很好理解,就和廣播一樣,如果你沒有提前開啓廣播,那麼廣播之前的東西你是收聽不到的。