Git 項目地址:https://github.com/anguoyoula/JMS
一、JMS簡介
1、什麼是JMS
JMS即ava消息服務(Java Message Service),通常是一個具體的消息中間件來實現具體消息服務。JMS允許兩個應用程序或系統之間解耦合的進行消息傳遞,JMS是消息傳遞機制的一個具體的規範。一搜一大堆。
二、JMS的Linux測試環境
1、Java開發環境
JDK:1.8
依賴:activemq-all 5.10.0
2、服務器環境
JDK:1.8
操作系統:Centos7
消息中間件:apache-activemq-5.15.6
3、搭建注意事項
- 沒有Linux環境就在Windows環境搭建,也許還簡單一些。
- Linux環境搭建需要注意防火牆的設置,推薦一篇關於防火牆命令的博文:https://www.cnblogs.com/moxiaoan/p/5683743.html。
- 依賴添加那個全家桶依賴就夠了,ActiveMQ依賴裏面有JMS接口。主要是方便、不出錯,學習的時候,不出錯最好。
- 如果中間件配置了認證的話,默認的用戶和密碼都是:admin。
三、JMS整體API架構
1、JMS整體API架構圖
2、JMS開發基本流程
結合上面的交互實例圖,JMS的開發流程代碼如下:
- 聲明連接工廠,用於獲取與消息服務器的連接:ConnectionFactory
- 通過連接工廠創建連接:Connection
- 通過連接創建會話:Session
消息發送者:- 通過會話創建消息發送者:MessageProducer
- 通過會話創建消息目的地:Destination
- 通過會話創建需要發送的消息:Message
- 通過MessageProducer send Message to Destination
消息接收者- 通過會話創建消息接收者:MessageConsumer
- 通過會話創建消息來源地:Destination
- 設置MessageConsumer的監聽器
- 開啓連接的消息接收
以上的流程僅僅是基本的流程,還有很多需要注意的細節,例如:
- 連接的認證信息
- 會話的事物控制、會話的單線程訪問權限控制(JMS規定一個Session只能夠被一個線程訪問,但是一個連接可以有多個Session)
- 消息的類型(文本?字節?對象?…)
- 消息的投遞模式
- 消息分組
- …
四、實例程序
這個實例開發以一個簡易的聊天室來學習JMS的基本開發流程,使用訂閱發佈來進行實例開發。
1、簡易說明消息傳送模型
詳細模型討論見我的另外一篇博文:https://blog.csdn.net/qq_37121463/article/details/83212732
①:點對點模型
每一條發送到消息中間件某個目的地(消息隊列)的消息只會被一個監聽該隊列的消息消費者消費,哪怕有多個消費者監聽這個消息隊列。每條消息只被一個消費者消費。
②:訂閱發佈模型
每一條發送到消息中間件某個目的地(主題)的消息會被已經訂閱該主題的消息消費者獲取,每一個消費者都會獲取到這個消息的備份。每條消息會被已經訂閱的消費者們消費。
2、程序邏輯簡述
- 程序啓動,獲取JMS服務的基本條件:獲取消息中間件的連接、創建會話。
- 訂閱聊天主題,以便獲取聊天室的聊天消息。
- 監聽下線提醒主題以收取其他的用戶下線消息提醒。
- 監聽上線提醒主題以收取其他的用戶上線消息提醒。
- 向上線提醒主題發送當前用戶上線消息,其他在線用戶將監聽到這個用戶上線的消息。
- 開啓連接,以便獲取主題的消息。
- 監聽輸入,發送到聊天主題。監聽退出。
- 向下線主題發送當前用戶下線提醒。
3、程序具體代碼如下
package jms.step1;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.util.Scanner;
public class ChatRoom {
public static void main(String[] args) throws JMSException{
/*
程序關閉命令
*/
final String exit = "exit";
/*
一個用於監聽其他人員登錄的主題,某個加入這個聊天室的人員都會收到一個比他後加入該聊天室的人員登錄提醒信息
*/
final String loginTopicName = "loginTopicName";
/*
一個用於監聽其他人員登出的主題,正在這個聊天室的人員都會收到一個比他先登出該聊天室的人員登出提醒信息
*/
final String logoutTopicName = "logoutTopicName";
/*
加入同一個聊天室的人員會向同一個聊天主題發送消息,同時也會訂閱該主題來獲取其他人員發送的消息
*/
final String chatMessageTopicName = "chatMessageTopicName";
/*
消息中間件服務器的連接url
*/
final String url = "tcp://192.168.161.151:61616";
/*
中間件服務器的認證信息
*/
final String userName = "admin";
final String password = "admin";
/*
當前用戶的名稱,用於標識消息來源
*/
String name;
Scanner scanner = new Scanner(System.in);
/*
獲取當前人員的標識
*/
System.out.println("請輸入你的名字:");
name = scanner.nextLine();
/////1.程序啓動,獲取JMS服務的基本條件:獲取消息中間件的連接、創建會話。
/*
獲取具體的連接工廠,這裏是用ActiveMQ,所以使用ActiveMQConnectionFactory連接工廠。
參數1、2:連接的認證信息,可以沒有,如果有但是這裏沒有配置的話,需要在創建連接的時候傳入認證信息
參數3:中間件服務器的連接url,沒有就是默認的本機61616端口
*/
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(userName,password,url);
/*
創建連接,上面連接工廠傳入了認證信息,所以沒有再傳入,如果連接工廠沒有傳入認證信息,但是消息服務中間件又需要
認證信息,這裏不傳入的話,將會報錯。
*/
Connection connection = connectionFactory.createConnection();
/*
創建會話,會話對象只能夠被一個線程使用。
參數1:是否開啓事物控制,詳細見我博客:https://blog.csdn.net/qq_37121463/article/details/83212759
參數2:消息的簽收模式,詳細見我博客:https://blog.csdn.net/qq_37121463/article/details/83212752
*/
Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
/////2.訂閱聊天主題,以便獲取聊天室的聊天消息。
/*
創建一個目的地,這個目的地是一個主題,當向這個主題發送消息時,如果中間件沒有這個主題,將創建這個主題。
*/
Destination chatMessageTopic = session.createTopic(chatMessageTopicName);
/*
創建一個消息消費者來消費這個主題的消息,即獲取聊天室裏面的信息。
第一個參數:目的地
第二個參數:消息選擇器,選取消息的規則表達式,詳細見我的博客:https://blog.csdn.net/qq_37121463/article/details/83212752
第三個參數:指明當前消費者是否可以接受"本地"消息.noLocal參數只對Topic類型的"目的地"有效。
如果消息消費者和生產者有一個Connection創建(即它們具有同一個ClientID,或者說底層是一個TCP鏈接),
即使它們是在不同的session中,它們均被認爲是“本地”。
在這裏我們要看見自己發的消息,所以第三個參數爲false,表示接受本地發出去的消息。同時我們監聽所有消息,沒有消息選擇。
*/
MessageConsumer chatMessageConsumer = session.createConsumer(chatMessageTopic,null,false);
/*
爲這個主題設置監聽器,當有消息被髮到這個主題時,這個監聽器將被調用,以獲取聊天室的信息,但是在connection沒有start之前
,消息無法被監聽。
*/
chatMessageConsumer.setMessageListener(message -> {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
/*
顯示聊天的信息,這些信息是多個應用組件之間的協商結果,簡單的說就是主要是這種消息,你就必須有什麼,必須怎麼發 ...
*/
try{
/*
每條聊天消息都必須將誰發的這條信息給放在屬性裏面
*/
String author = textMessage.getStringProperty("author");
/*
獲取消息裏面的消息,也就是放在裏面的聊天信息
*/
String content = textMessage.getText();
System.out.println(author + " : " + content);
}catch(JMSException e){
e.printStackTrace();
}
}
});
/////3.監聽下線提醒主題以收取其他的用戶下線消息提醒
Destination logoutTopic = session.createTopic(logoutTopicName);
MessageConsumer logoutMessageConsumer = session.createConsumer(logoutTopic,null,false);
logoutMessageConsumer.setMessageListener(message -> {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try{
String author = textMessage.getText();
System.out.println(author + "\t下線了");
}catch(JMSException e){
e.printStackTrace();
}
}
});
/////4.監聽上線提醒主題以收取其他的用戶上線消息提醒。
Destination loginTopic = session.createTopic(loginTopicName);
MessageConsumer loginMessageConsumer = session.createConsumer(loginTopic,null,false);
loginMessageConsumer.setMessageListener(message -> {
if(message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try{
String author = textMessage.getText();
System.out.println(author + "\t上線了");
}catch(JMSException e){
e.printStackTrace();
}
}
});
/////5.向上線提醒主題發送當前用戶上線消息,其他在線用戶將監聽到這個用戶上線的消息。
/*
創建一個消息提供者,用於發送當前用戶的上線提醒
*/
MessageProducer loginMessageProducer = session.createProducer(loginTopic);
/*
創建一個文本類型的消息,可以直接傳參數創建一個等待發送的文本消息,也可以創建一個空參文本消息,再進行set設置
*/
TextMessage userLoginMessage = session.createTextMessage(name);
/*
發送這個文本消息。
void send(Message message,int deliveryMode,int priority,long timeToLive);
發送消息,併爲此消息指定"傳輸模式"和"消息權重".如果某條消息需要特定聲明這些屬性,那麼你可以使用此方法.
void send(Destinantion des,Message message): 向指定的目的地,發送消息;
這個方法並不常用,通常用在"請求-應答"模式中:即消息消費者接收到消息之後,需要臨時創建一個Producer並將"應答消息"發送到指定的"replyTo"地址.
傳輸模式見我博客:https://blog.csdn.net/qq_37121463/article/details/83212752
API解釋見:https://www.cnblogs.com/whsa/p/4223728.html
*/
loginMessageProducer.send(userLoginMessage);
/////6.開啓連接,以便獲取主題的消息。
/*
開啓連接接收消息,使用connection.stop();關閉消息接收,關閉後可以再次調用start開啓消息接收。注意:close是關閉連接。
*/
connection.start();
/////7.監聽輸入,發送到聊天主題。監聽退出
/*
創建一個消息提供者來進行聊天消息發送
*/
MessageProducer chatMessageProducer = session.createProducer(chatMessageTopic);
String content = null;
while(true){
content = scanner.nextLine();
if(content == null || content.length() < 1){
continue;
}else {
if(content.equals(exit)){
/*
發送離線通知
*/
MessageProducer logoutMessageProducer = session.createProducer(logoutTopic);
TextMessage logoutTextMessage = session.createTextMessage(name);
logoutMessageProducer.send(logoutTextMessage);
/*
退出系統
*/
break;
}
/*
發送消息
*/
TextMessage chatMessage = session.createTextMessage(content);
/*
設置消息屬性爲當前用戶
*/
chatMessage.setStringProperty("author",name);
/*
發送聊天消息
*/
chatMessageProducer.send(chatMessage);
}
}
System.out.println("再見:" + name);
System.exit(-1);
}
}
五、總結
- 開發流程基本上是固定的,特別的一點,Message不能自己new。
- 記住JMS的API/接口交互示例圖就成功大半。
- 訪問中間件管理頁面鏈接:http://[ip]:8161/admin/
路漫漫其修遠兮,吾將上下而求索。