RabbitMQ学习笔记
闲来无事,最近学习了RabbitMQ,写了简单的工程。
下面关于MQ的信息是来自于百度百科:
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。其中较为成熟的MQ产品有IBM WEBSPHERE MQ等等。
MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。MQ和JMS类似,但不同的是JMS是SUN JAVA消息中间件服务的一个标准和API定义,而MQ则是遵循了AMQP协议的具体实现和产品。
RabbitMQ是一个在AMQP基础上完整的,可复用的企业消息系统。他遵循Mozilla Public License开源协议。
关于RabbitMQ服务器的安装这里不再阐述,有情趣者可以自行尝试或百度搜索.
RabbitMQ 官方下载地址: http://www.rabbitmq.com/download.html
下面开始阐述:
1.工程结构:
2.添加jar包
spring
rabbit-client
spring jar包的添加不再列出,这里仅列出rabbit-client的依赖:
<dependency>
<groupId>rabbitmq-client</groupId>
<artifactId>rabbitmq-client</artifactId>
<version>3.5.6</version>
</dependency>
首先是ConnectionFactoryUtil.java类:
public class ConnectionFactoryUtil {
/**
*
*返回ConnectionFactory 连接工厂
*/
public static ConnectionFactory get() {
ConnectionFactory cfconn = new ConnectionFactory();
cfconn.setHost("127.0.0.1");
cfconn.setPort(5672);
cfconn.setUsername("admin");
cfconn.setPassword("admin");
//设置虚拟主机
cfconn.setVirtualHost("vhost1");
//设置线程池,为了避免产生意想不到的异常,这里将线程池大小设为1.
//因为多个线程之间共享Channel时,有些操作会出现问题。如:可能会影响发布者对确认消息的接收
//这样设置也是和MqFactory.java类相关的,因为Channel在运行时是单例的.
cfconn.setSharedExecutor(Executors.newFixedThreadPool(1));
return cfconn;
}
}
MqFactory.java 类:
@Component
@Scope("singleton")
public class MqFactory implements BeanDefinitionRegistryPostProcessor {
private static Logger log = LoggerFactory.getLogger(MqFactory.class);
private static Channel channel = null;
private static Map<String, Boolean> hasBind = new HashMap<String, Boolean>();
/**
* 获取通道
* @return
* @throws IOException
* @throws TimeoutException
*/
private Channel getConnection() throws IOException, TimeoutException {
if (channel == null) {
ConnectionFactory factory = ConnectionFactoryUtil.get();
channel = factory.newConnection().createChannel();
}
return channel;
}
/**
* 发布消息
* @param msg 发布的对象(消息)
*/
public void publishMessage(Object msg) {
try {
//队列名(自定义)
String queueName = "admin";
//交换机名(自定义)
String exchange = "optimus_exchange";
//路由key
String routingKey = queueName + "_routingKey";
Channel channel = getConnection();
//声明交换机
channel.exchangeDeclare(exchange, "direct", true);
//声明队列
channel.queueDeclare(queueName, true, false, false, null);
//将队列和交换机绑定
channel.queueBind(queueName, exchange, routingKey);
//序列化
byte[] buf = ObjectUtil.serializeJdk(msg);
//发布消息
//MessageProperties.PERSISTENT_TEXT_PLAIN 消息属性
channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, buf);
} catch (Exception e) {
log.error(e.getMessage(), e);
try {
channel = null;
hasBind.clear();
Thread.sleep(2000);
} catch (Exception e2) {
}
}
}
//ioc 容器
private ConfigurableListableBeanFactory factory;
// MqConsumer 所有的实现类
private static List<MqConsumer> queueList = new ArrayList<>();
//线程池
private static ExecutorService workers = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//MqFactory初始化方法
//获取MqConsumer实现类,调用消费消息的方法.
public void init() {
queueList.clear();
Map<String, MqConsumer> map = factory.getBeansOfType(MqConsumer.class);
for (Map.Entry<String, MqConsumer> entry : map.entrySet()) {
MqConsumer consume = entry.getValue();
queueList.add(consume);
}
/**
* MqFactory仅是一个简单的实现,交换机和队列都只有一个,且是一对一绑定的
*
* 此方法是发布着发布消息成功后,消费者进行消费消息
*/
doConsumerTask(queueList);
}
private void doConsumerTask(final List<MqConsumer> consumerList) {
//用了两个死循环
//第一个是为了做到获取连接失败或成功消费消息后,仍继续连接队列消费消息.
//第二个是为了将发布者发布的消息,发送给所有关注了对应队列的消费者(实现了MqConsumer实现类)
//这里只有一个队列,没有复杂的模块划分,所有这里的实现简单一些
workers.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
ConnectionFactory connFactory = ConnectionFactoryUtil.get();
Connection conn = connFactory.newConnection();
Channel channel = null;
QueueingConsumer consumer = null;
try {
channel = conn.createChannel();
channel.basicQos(1);
consumer = new QueueingConsumer(channel);
channel.basicConsume("admin", false, "", false, false, null, consumer);
} catch (Exception e) {
System.out.println("无法连接");
try {
// 暂停2秒后重连
Thread.sleep(2000);
} catch (Exception e2) {
}
continue;
}
// 连接成功后,开始消费消息
while (true) {
try {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
Object message = ObjectUtil.deserializeJdk(delivery.getBody());
for (MqConsumer mc : consumerList) {
try {
mc.comsum(message);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
// ack
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
} catch (Exception e) {
log.error("rabbitmq error", e);
try {
channel.abort();
conn.abort();
} catch (Exception e2) {
}
break;
}
}
} catch (Exception e) {
continue;
}
}
}
});
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
this.factory = arg0;
}
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry arg0) throws BeansException {
}
}
MqFactory 实现了 BeanDefinitionRegistryPostProcessor这个接口,意在将ConfigurableListableBeanFactory 它的实例注入进来. 以此来获取所有实现MqConsumer接口的类.
MqInit.java
@Service
public class MqInit implements InitializingBean{
@Autowired
private MqFactory factory;
public MqInit(){
}
@Override
public void afterPropertiesSet() throws Exception {
this.factory.init();
}
}
MqInit.java 主要是执行MqFactory实例的初始化方法的.
总结:
对于上面的代码而言:
针对特定的业务逻辑处理类,只需要将MqFactory注入进去,调用发布消息的方法,然后再实现MqConsumer接口(要交由spring来管理)。
这样在程序运行时,只要有发布者发布了消息,MqConsumer.java的实现类都能够收到消息并处理
MqConsumer.java接口是用来处理消息的.
下面是一个简单的处理,将消息直接打印出来:
@Service
public class ConsumerOne implements MqConsumer {
@Override
public void comsum(Object msg) {
System.out.println(msg);
}
}
运行:
以Hello World为例:
public class App
{
public static void main( String[] args ) throws IOException, TimeoutException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:config/spring-view.xml");
MqFactory factory = context.getBean(MqFactory.class);
factory.publishMessage("Hello World!!!!!!!!");
}
}
输出:
大功告成!!!!!!!!!!!!!!!!!!!!!!!!!!!!!