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