Marco's Java【ActiveMQ入門(二) 之 使用Java操作ActiveMQ】

前言

前面一節,我們提到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();
		} 
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章