---------------------------- BEGIN ---------------------------------
1、訊息(Message): 是指在應用間傳送的資料。訊息可以非常簡單,比如只包含文字字串,也可以更復雜,可能包含嵌入物件。
2、訊息佇列(Message Queue):是一種應用間的通訊方式,訊息傳送後可以立即返回,由訊息系統來確保訊息的可靠傳遞。訊息釋出者只管把訊息釋出到 MQ 中而不用管誰來取,訊息使用者只管從 MQ 中取訊息而不管是誰釋出的。這樣釋出者和使用者都不用知道對方的存在。
3、AMQP :Advanced Message Queue,高階訊息佇列協議。 它是應用層協議的一個開放標準,為面向訊息的中介軟體設計,基於此協議的客戶端與訊息中介軟體可傳遞訊息,並不受產品、開發語言等條件的限制。
4、RabbitMQ :是一個由 Erlang 語言開發的 AMQP 的開源實現.
5、Exchange 型別:Exchange分發訊息時根據型別的不同分發策略有區別,目前共四種型別:direct、fanout、topic、headers 。headers 匹配 AMQP 訊息的 header而不是路由鍵,此外 headers 交換器和 direct交換器完全一致,但效能差很多,目前幾乎用不到了。
6、RabbitMQ 安裝:一般來說安裝 RabbitMQ 之前要安裝 Erlang ,可以去Erlang官網下載。接著去RabbitMQ官網下載安裝包,之後解壓縮即可。根據作業系統不同官網提供了相應的安裝說明:Windows、Debian / Ubuntu、RPM-based Linux、Mac;具體安裝百度
7、Java 客戶端訪問: a、maven工程的pom檔案中新增依賴
<!-- RabbitMQ依賴 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.1.0</version>
</dependency>
複製程式碼
b.1、新建一個 抽象 rabbitmq連線通道 類:ConnectionChannel
package com.aaa.bbb.ccc.ddd;
import java.io.IOException;
import com.goldpac.config.JGroupsConfig;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @Function : 抽象 rabbitmq連線通道 類
* @Author & @Date : lynn_ - 2018年06月14日
*/
public abstract class ConnectionChannel {
//安裝 RabbitMQ 的主機IP
protected static final String HOST = "127.0.0.1";
protected static final String USER = "test";
protected static final String PASSWORD = "test";
//訊息服務請求URL
public static final String CMS_HOST = "http://" + HOST + ":8080";
// routingKey
public static final String ROUTING_KEY = "YOU.SELF.KEY";
// 連線
protected Connection connection;
// 連線通道
protected Channel channel;
// 連線通道路由地址
protected String routingKey;
// 交換機名稱
protected final static String EXCHANGE_NAME = "cms";
// 構造方法; 接收一個路由地址引數
public ConnectionChannel(String routingKey) throws Exception {
this.routingKey = routingKey;
// 建立一個連線工廠 connection factory
ConnectionFactory factory = new ConnectionFactory();
// 設定rabbitmq-server服務IP地址、使用者名稱、密碼、埠
factory.setHost(HOST);
factory.setUsername(USER);
factory.setPassword(PASSWORD);
factory.setPort(5672); //預設埠
factory.setVirtualHost("/");
// 宣告一個連線
connection = factory.newConnection();
// 宣告訊息通道
channel = connection.createChannel();
/*
* 宣告轉發器 - 定義一個交換機 引數1:交換機名稱 引數2:交換機型別 引數3:交換機永續性,如果為true則伺服器重啟時不會丟失
* 引數4:交換機在不被使用時是否刪除 引數5:交換機的其他屬性
*/
channel.exchangeDeclare(EXCHANGE_NAME, "topic", false, false, null);
}
/**
* 關閉channel和connection; 非必須,因為隱含是自動呼叫的。
* @throws IOException
*/
public void close() throws Exception {
this.channel.close();
this.connection.close();
}
}
複製程式碼
b.2、新建一個 訊息生產傳送 類:Sender
package com.aaa.bbb.ccc.ddd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Function : 傳送訊息 - 生產者
* @Author & @Date : lynn_ - 2018年06月14日
*/
public class Sender extends ConnectionChannel {
//日誌列印 - 所有logger.info()
private final Logger logger = LoggerFactory.getLogger(getClass());
//持久化 佇列 名稱
private String queueName;
/**
* Creates a new instance of Sender
* @param routingKey
* @throws Exception
*/
public Sender(String routingKey) throws Exception {
super(routingKey);
this.queueName = "queue_topic";
}
public Sender(String routingKey, String queueName) throws Exception {
super(routingKey);
this.queueName = queueName;
}
/**
* @Title : sendMessage
* @Function: 往轉發器[交換機]上傳送訊息
* @param byte[]
* @throws Exception
*/
public void sendMessage(byte[] bodys) throws Exception{
//宣告一個佇列 - 持久化
channel.queueDeclare(queueName, true, false, false, null);
//設定通道預取計數
channel.basicQos(1);
//將訊息佇列繫結到Exchange
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
/**
* 傳送訊息到佇列中
* 引數1:交換機exchange名字,若為空則使用預設的exchange[]
* 引數2:routing key - 路由地址
* 引數3:其他的屬性
* 引數4:訊息體
* RabbitMQ預設有一個exchange,叫default exchange,它用一個空字串表示,它是direct exchange型別,
* 任何發往這個exchange的訊息都會被路由到routing key的名字對應的佇列上,如果沒有對應的佇列,則訊息會被丟棄
*/
channel.basicPublish(EXCHANGE_NAME, routingKey, null, bodys);
logger.info("PDM訊息傳送成功 -- [ " + EXCHANGE_NAME + " ] - " + routingKey);
}
}
複製程式碼
b.3、新建一個 接收訊息 - 消費者 類:Receiver
package com.aaa.bbb.ccc.ddd;
import com.rabbitmq.client.QueueingConsumer;
/**
* @Function : 接收訊息 - 消費者
* @Author & @Date : lynn_ - 2018年06月14日
*/
@SuppressWarnings("deprecation")
public class Receiver extends ConnectionChannel {
private String queueName;
/**
* Creates a new instance of Receiver
* @param routingKey
* @throws Exception
*/
public Receiver(String routingKey) throws Exception {
super(routingKey);
this.queueName = "queue_topic";
}
public Receiver(String routingKey, String queueName) throws Exception {
super(routingKey);
this.queueName = queueName;
}
/**
* @Title : getMessage
* @Function: 從 交換機 上獲取訊息
* @throws Exception
*/
public void getMessage() throws Exception{
//宣告一個臨時佇列,該佇列會在使用完比後自動銷燬 - 非必需
queueName = channel.queueDeclare().getQueue();
// - 宣告要關注的佇列 - 非必需
//channel.queueDeclare(queueName, true, false, false, null);
//server push訊息時的佇列長度 - 同一時刻伺服器只會發一條訊息給消費者 - 非必需
channel.basicQos(1);
//將訊息佇列繫結到Exchange - 將佇列繫結到交換機 - 繫結一個routing key
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
//宣告消費者 - 用來快取伺服器推送過來的訊息
QueueingConsumer consumer = new QueueingConsumer(channel);
/*
* 監聽佇列,手動返回完成 - 為channel宣告一個consumer,伺服器會推送訊息
* 引數1:持久化佇列名稱
* 引數2:是否傳送ack包,不傳送ack訊息會持續在服務端儲存,直到收到ack。 可以通過channel.basicAck手動回覆ack
* 引數3:消費者
*/
channel.basicConsume(queueName, false, consumer);
//wait for the next message delivery and return it
while (true) {
//獲取訊息,如果沒有訊息,這一步將會一直阻塞
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
//獲取訊息主體資料
byte[] bytes = delivery.getBody();
//路由地址
String routingKey = delivery.getEnvelope().getRoutingKey();
//對訊息主體進行處理
//我這裡新建了一個專用於處理訊息的類ReceiverHandler -- 後面給出程式碼
ReceiverHandler.HandleMessage(bytes, routingKey);
//確認訊息已經收到 - 回覆ack包,如果不回覆,訊息不會在伺服器刪除
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
複製程式碼
b.4、現在開始測試:...
1)、測試訊息傳送類 Test_Send
package package com.aaa.bbb.ccc.ddd.test;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.goldpac.ito.pdm.api.plm._rabbitmq.Sender;
import com.goldpac.ito.pdm.api.plm.vo.CardNodeObject;
/**
* @Function : 測試 往轉發器[交換機]上傳送訊息
* @Author & @Date : lynn_ - 2018年06月14日
*/
public class Test_Send {
public static void main(String[] args) {
run();
}
void run(){
//傳送訊息資料物件類 - 傳送訊息到轉發器[交換機]佇列中的訊息資料載體
final CardNodeObject object = new CardNodeObject();
byte[] bodys = null;
//資料準備
object.setMessage("success");
object.setType("1");
Map<String, Object> m = new HashMap<String, Object>();
m.put("params1", "123456789");
m.put("params1", "987654321");
m.put("params1", "備註說明");
object.setData(m);
try {
//將資料物件object 轉換成JSON字串格式
String jsonStr = JSON.toJSONString(object);
System.out.println("PDM生產者傳送訊息!" + " --- '" + jsonStr+"'");
//用 UTF-16LE 解碼字串 - C#中的 Unicode 編碼對應 JAVA 中的是 UTF-16LE 格式
bodys = jsonStr.getBytes("UTF-16LE");
//例項化一個 傳送訊息 的生產者; 傳入 需要繫結的routingKey
Sender sender = new Sender("YOU.SELF.KEY");
sender.sendMessage(bodys);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
複製程式碼
2)、測試 接收交換機推送的訊息類 Test_Rec
package package com.aaa.bbb.ccc.ddd.test;
import com.goldpac.ito.pdm.api.plm._rabbitmq.Receiver;
/**
* @Function : 測試 接收 交換機推送的訊息
* @Author & @Date : lynn_ - 2018年06月14日
*/
public class Test_Rec {
public static void main(String[] args) {
run();
}
void run(){
try {
//傳入 監聽的routingKey -
Receiver receiver = new Receiver("YOU.SELF.KEY");
receiver.getMessage();
} catch (Exception e) {
e.printStackTrace();
}
}
複製程式碼
}
b.5、附上 CardNodeObject 類...
package com.aaa.bbb.ccc.ddd;
import java.io.Serializable;
/**
* @Function : 訊息佇列 - 傳送訊息資料物件類 - 傳送訊息到轉發器[交換機]佇列中的訊息資料載體
* @Author & @Date : lynn_ - 2018年06月14日
*/
public class CardNodeObject implements Serializable{
private static final long serialVersionUID = 3439055380741158411L;
/**
* 訊息型別
*/
private String type;
/**
* 訊息提示資料 default : success
*/
private String message;
/**
* 附帶的資料物件{} default : null
*/
private Object data;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
複製程式碼
b.5、附上 b.3 中呼叫的 ReceiverHandler 類...
package ...;
/**
* @Function : 接收訊息 - 消費者 - 資料處理
* @Author & @Date : lynn_ - 2018年06月14日
*/
public class ReceiverHandler {
//新增日誌列印 - 所有logger.info()
private final static Logger logger = LoggerFactory.getLogger(ReceiverHandler.class.getName());
/**
* @Title : HandleMessage
* @Function: 消費者接收伺服器推送的訊息後,對訊息主體進行處理
* @param bytes
* - 訊息主體, 位元組陣列
* @param routingKey
* - 接收訊息通道的路由鍵
* @throws UnsupportedEncodingException
*/
public static void HandleMessage(byte[] bytes, String routingKey) throws UnsupportedEncodingException {
if (null == bytes || bytes.length == 0) {
logger.error("message (delivery.getBody() - bytes) is null or length == 0");
return;
}
logger.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " - 接收到稿樣卡款訊息 routingKey:" + routingKey);
// 將訊息主體轉成字串格式; C#中的 Unicode 編碼對應 JAVA 中的是 UTF-16LE 格式
String msg = new String(bytes, "UTF-16LE");
// 根據 routingKey, 呼叫不同的處理方法
if (null != routingKey && StringUtils.isNotBlank(msg)) {
// 根據需要做具體處理...
}
}
複製程式碼
}
---------------------------- END ---------------------------------