目錄
JMS 基本概念
提示:可以先參考《ActiveMQ 簡介 與 Maven 項目基本使用 》的編碼,然後更容易理解本文的理論。
1、JMS 全稱 Java Message Service ,Java 消息服務,是 Java EE 中的一個技術。
2、JMS 規範定義了 Java 中訪問消息中間件的接口,並沒有給予實現,實現 JMS 接口的消息中間件成爲 JMS Provider(供應者),例如 ActiveMQ 。
3、JMS Provider:實現 JMS 接口和規範的消息中間件。
4、JMS Message:JMS 的消息,由以下三個部分組成:
1)消息頭:每個消息頭字段都有對應的 getter 和 setter 方法
2)消息屬性:如果需要除消息頭字段以外的值,那麼可以使用消息屬性
3)消息體:封裝具體的消息數據
5、JMS Producer:消息生產者,創建和發送 JMS 消息的客戶端應用
6、JMS Consumer:消息消費者,接收和處理 JMS 消息的客戶端應用。消息的消費可以採用以下兩種方式之一:
1)同步消費:通過調用消費者的 receive 方法從目的地中顯示的讀取消息數據,receive 方法會一直阻塞到消息到達。
2)異步消費:客戶端可以爲消費者註冊一個消息監聽器,以定義消息到達時所採取的措施/動作。
7、JMS domains:消息傳遞域,JMS 規範中定義了兩種消息轉遞域:點對點(point-to-point,簡寫成 PTP),發佈/訂閱(publish/subscribe,簡寫爲 pub/sub)
8、點對點(point-to-point)消息傳遞域特點如下:
1)每個生產可以對應多個消費者,但是每個消息只能有一個消費者消費
2)消息的生產者和消費者之間沒有時間上的相關性,即無論消費者在生產者發送消息的時候是否已經運行,它啓動後都可以提取消息
9、發佈/訂閱(publish/subscribe)消息傳遞域特點如下:
1)每個消息可以有多個消費者消費
2)生產者和消費者之間有時間上的相關性。訂閱一個主題的消費者只能消費自它訂閱之後生產者發佈的消息。即使消費者訂閱了主題,如果生產者發佈消息的時候,消費者宕機了,那麼它再次啓動的時候,默認也是不能再接收的。但 JMS 規範允許客戶端創建持久訂閱,允許消費者消費它在未激活狀態時發送的消息。
10、在點對點消息傳遞域中,目的地被稱爲隊列(queue),在發佈/訂閱消息傳遞域中,目的地被稱爲主題(topic)
11、Connection Factory:連接工廠,用來創建連接對象,以連接到 JMS 的 Provider(供應商,實現者)
12、JMS Connection:封裝了客戶與 JMS 提供者之間的一個虛擬的連接
13、JMS Session:是生產者和消費者的一個單線程上下文。會話用於創建消息生產者(Producer)、消息消費者(Consumer),和消息(Message)等。會話提供了一個事務性的上下文,一組發送和接收被組合到了一個原子操作中。
Destination | 消息發送到的目的地 |
Acknowledge | 簽收 |
Transaction | 事務 |
JMS Cline | 用來收發消息的客戶端應用 |
JMS 消息結構
1、JMS 消息(Message)由消息頭、消息屬性、消息體組成。
2、消息頭包含消息的識別信息和路由信息,包含一些標準的屬性如下:
標準屬性 | 描述 |
JMSDestination | JMS 目的地,由 send 方法設置。主要指 queue與topic。自動分配。 |
JMSDeliveryMode | JMS 傳遞模式,由 send 方法設置。分爲持久模式和非持久模式。前者的消息應該被傳送"一次且僅僅一次",即使 JMS 提供者出現故障,該消息也不會丟失,會在服務器恢復後再次傳遞;後者的消息最多會傳送一次,如果JMS Provider 服務器故障,該消息將永久丟失。自動分配。 |
JMSExpiration | JMS 消息過期/到期時間,由 send 方法設置。等於 Destination 的 send 方法中的 timeToLive 值加上發送時刻的 GMT 時間值。如果 timeToLive 等於0,則 JMSExpiration 被設爲0,表示該消息永不過期。如果發送後,在消息過期時間之後還沒有被髮送到目的地,則該消息被清除。自動分配。 |
JMSPriority | JMS 消息優先級,由 send 方法設置。有 0-9 十個級別,0-4是普通消息,5-9是加急消息。JMS 不要求 JMS Provider 嚴格按着十個優先級發送消息,但必須保證加急消息要先於普通消息到達。默認是第4級。自動分配。 |
JMSMessageID | JMS 消息標識 id,由 send 方法設置。唯一識別每個消息的標識,由 JMS Provider 產生。自動分配。 |
JMSTimestamp | JMS 時間戳,由客戶端設置。一個 JMS Provider 在調用 send 方法時自動設置的。它是消息被髮送和消費者實際接收的時間差。自動分配。 |
JMSCorrelationID | JMS 相關性 id,由客戶端設置。用來連接到另外一個消息,典型的應用是在回覆消息中連接到原消息。可以是任意值,不僅僅是 JMSMessageId。 |
JMSType | JMS 消息類型的識別符。由開發者設置。 |
JMSReplyTo | JMS 回覆,由客戶端設置。提供本信息回覆消息的目的地址。 |
JMSRedelivered | JMS 重發,由JMS Provider(供應商)設置 |
3、消息體:JMS API 定義了 5 種消息體格式,也叫消息類型,可以使用不同的形式發送接收數據,並可以兼容現有的消息格式。包括:TextMessage、MapMessage、BytesMessage、StreamMessage 和 ObjectMessage。它們都是 Message 接口的子類。
4、消息屬性,包含以下三種類型的屬性:
1)應用程序設置和添加的屬性,比如:Message.setStringProperty("username","zhangSan");
2)JMS 定義的屬性:使用 "JMSX" 作爲屬性名的前綴,connection.getMetaData().getJMSXPropertyNames(); 返回連接支持的所有 JMSX 屬性的名字;
3)JMS 供應商特點的屬性
JMSXUserID | 發送消息的用戶標識,發送時提供商設置 |
JMSXAppID | 發送消息的應用標識,發送時提供商設置 |
JMSXDeliveryCount | 轉發消息重試次數,第一次是1、第二次是2、...,發送時提供商設置 |
JMSXGroupID | 消息所在消息組的標識,由客戶端設置 |
JMSXGroupSeq | 組內消息的序號,第一個消息是1,第二個是2,...,由客戶端設置 |
JMSXProducerTXID | 產生消息的事務的事務標識,發送時提供商設置 |
JMSXConsumerTXID | 消費消息的事務的事務標識,接收時提供商設置 |
JMSXRcvTimestamp | JMS 轉發消息到消費者的時間,接收時提供商設置 |
String brokerURL = "tcp://127.0.0.1:61616";//ActiveMQ 中間件連接地址
/**
* 創建 javax.jms.ConnectionFactory 連接工廠
* org.apache.activemq.ActiveMQConnectionFactory 中默認設置了大量的參數,還有幾個重載的構造器可以選擇
*/
ConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
//如果 ActiveMQ 連不上,則拋異常:java.net.ConnectException: Connection refused: connect
Connection connection = mqConnectionFactory.createConnection();//通過連接工廠獲取連接 javax.jms.Connection
connection.start();//啓動連接,同理還有 stop、close
//連接元數據
ConnectionMetaData metaData = connection.getMetaData();
int jmsMajorVersion = metaData.getJMSMajorVersion();
int jmsMinorVersion = metaData.getJMSMinorVersion();
String jmsProviderName = metaData.getJMSProviderName();
String jmsVersion = metaData.getJMSVersion();
int providerMajorVersion = metaData.getProviderMajorVersion();
int providerMinorVersion = metaData.getProviderMinorVersion();
String providerVersion = metaData.getProviderVersion();
Enumeration jmsxPropertyNames = metaData.getJMSXPropertyNames();
System.out.println("jmsMajorVersion = " + jmsMajorVersion);//jmsMajorVersion = 1
System.out.println("jmsMinorVersion = " + jmsMinorVersion);//jmsMinorVersion = 1
System.out.println("jmsProviderName = " + jmsProviderName);//jmsProviderName = ActiveMQ
System.out.println("jmsVersion = " + jmsVersion);//jmsVersion = 1.1
System.out.println("providerMajorVersion = " + providerMajorVersion);//providerMajorVersion = 5
System.out.println("providerMinorVersion = " + providerMinorVersion);//providerMinorVersion = 15
System.out.println("providerVersion = " + providerVersion);//providerVersion = 5.15.9
while (jmsxPropertyNames.hasMoreElements()){
Object o = jmsxPropertyNames.nextElement();
System.out.print(o+"\t");//JMSXUserID JMSXGroupID JMSXGroupSeq JMSXDeliveryCount JMSXProducerTXID
}
JMS 消息確認模式
1、JMS 消息只有在被確認之後,才認爲已經成功的消費了。消息的成功消費通常包含三個階段:客戶接收消息、客戶處理消息、消息被確認。消息被確認後,消費者無法再次讀取,相反如果消息未被確認,則消費者可以一直讀取。
2、createSession(boolean transacted, int acknowledgeMode)
transacted 爲 true 時:表示事務性會話,此時當一個事務被提交時,確認自動發生:session.commit();//確認消息,告訴中間件,消息已經確認接收;與 acknowledgeMode 參數設置無關。
transacted 爲 false 時:表示非事務性會話,此時消息何時被確認取決於創建會話時的應答模式(acknowledgeMode)。與 acknowledgeMode 參數有關。
3、應答/確認模式(acknowledgeMode) 在 transacted 爲 false 時有效,可選值如下:
Session.AUTO_ACKNOWLEDGE | 自動確認。當客戶成功的從 receive 方法,或者從 MessageListener.onMessage 方法返回的時候,會話自動確認客戶收到的消息。 |
Session.CLIENT_ACKNOWLEDGE | 客戶確認。客戶通過調用消息(Session)的 acknowledge() 方法確認消息。client 確認模式是在會話層上進行,只要確認一個被消費的消息,將自動確認當前會話中所有已經被消費的消息。比如一個會話中消費者消費了10個消息,然後確認了第8個消息,那麼這10個消息都會被確認。 |
Session.DUPS_ACKNOWLEDGE | 遲鈍確認。該模式只是會話遲鈍的確認消息的提交。如果 JMS Provider 失敗,那麼可能會導致一些重複的消息。如果是重複的消息,那麼 JMS provider 必須把消息頭的 JMSRedelivered 字段設置爲 true。不常用。 |
4、下面使用環境:Maven 3.6.1 + Java JDK 1.8 + ActiveMQ 5.15.9 + IDEA 2018 稍作演示,Mava 管理的 Java SE 應用。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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wmx</groupId>
<artifactId>activeMQ1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
</dependencies>
</project>
生產者內容如下:
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.util.UUID;
/**
* 消息發送者
*/
@SuppressWarnings("all")
public class JmsSender {
public static void main(String[] args) {
Connection connection = null;
Session session = null;
try {
String brokerURL = "tcp://127.0.0.1:61616";//ActiveMQ 中間件連接地址
/**
* 創建 javax.jms.ConnectionFactory 連接工廠
* org.apache.activemq.ActiveMQConnectionFactory 中默認設置了大量的參數,還有幾個重載的構造器可以選擇
*/
ConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
//如果 ActiveMQ 連不上,則拋異常:java.net.ConnectException: Connection refused: connect
connection = mqConnectionFactory.createConnection();//通過連接工廠獲取連接 javax.jms.Connection
connection.start();//啓動連接,同理還有 stop、close
/**
* Session createSession(boolean transacted, int acknowledgeMode) 創建會話
* transacted :表示是否開啓事務
* acknowledgeMode:表示會話確認模式
*/
session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
/**
* createQueue(String queueName):創建消息隊列,指定隊列名稱,消費者可以根據隊列名稱獲取消息
* Destination 目的地,重點,interface Queue extends Destination
*/
Destination destination = session.createQueue("queue-app");
//createProducer(Destination destination):根據目的地創建消息生產者
MessageProducer producer = session.createProducer(destination);
int massageTotal = 5;
for (int i = 0; i < massageTotal; i++) {
MapMessage mapMessage = session.createMapMessage();//創建一個 Map 消息
mapMessage.setStringProperty("token", UUID.randomUUID().toString());//設置屬性。口令
mapMessage.setIntProperty("expires", 60000);////設置屬性。口令失效時間
mapMessage.setString("key", "嫦娥 " + (i + 1) + " 號");//設置消息內容
producer.send(mapMessage);//生產者發送消息
session.commit();//會話提交,發送消息
}
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();//關閉會話
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();//關閉連接
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}
消費者內容如下:
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消費者
*/
@SuppressWarnings("all")
public class JmsReceiver {
public static void main(String[] args) {
Connection connection = null;
Session session = null;
try {
String brokerURL = "tcp://127.0.0.1:61616";//ActiveMQ 中間件連接地址
/**
* 創建 javax.jms.ConnectionFactory 連接工廠
* org.apache.activemq.ActiveMQConnectionFactory 中默認設置了大量的參數,還有幾個重載的構造器可以選擇
*/
ConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory(brokerURL);
//如果 ActiveMQ 連不上,則拋異常:java.net.ConnectException: Connection refused: connect
connection = mqConnectionFactory.createConnection();//通過連接工廠獲取連接 javax.jms.Connection
connection.start();//啓動連接,同理還有 stop、close
/**
* Session createSession(boolean transacted, int acknowledgeMode) 創建會話
* transacted :表示是否開啓事務
* acknowledgeMode:表示會話確認模式
* 關閉事務,自動確認:表示 receive 方法、MessageListener.onMessage 方法返回後就已經確認了
*/
session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
/**
* createQueue(String queueName):創建消息隊列,指定隊列名稱,消費者可以根據隊列名稱獲取消息
* Destination 目的地,重點,interface Queue extends Destination
*/
Destination destination = session.createQueue("queue-app");
//createProducer(Destination destination):根據目的地創建消息消費者
MessageConsumer consumer = session.createConsumer(destination);
int massageTotal = 5;
for (int i = 0; i < massageTotal; i++) {
Message message = consumer.receive();//receive方法會導致當前線程阻塞,指導接收到消息
if (message instanceof MapMessage) {
MapMessage mapMessage = (MapMessage) message;//消費者接收消息。因爲對方發送的 Map 消息,所以可以強轉
System.out.print((i + 1) + ":" + mapMessage.getStringProperty("token"));//獲取屬性
System.out.print(":" + mapMessage.getIntProperty("expires"));//獲取屬性
System.out.println(":" + mapMessage.getString("key"));//獲取消息內容
mapMessage.acknowledge();//應答/確認消息
}
}
} catch (JMSException e) {
e.printStackTrace();
} finally {
if (session != null) {
try {
session.close();//關閉會話
} catch (JMSException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();//關閉連接
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
}