---------------------------- 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 ---------------------------------