rabbitmq作为消息队列,在实际应用中很常见,生产者将消息发送到某个队列,消费者消费这个队列。
消息在队列中,消费者要消费,需要监听队列,简单的来说,就是注册一个方法到消息通道,这个方法就会在有消息的时候执行。
下面通过java来操作rabbitmq,给出代码示例。
这里介绍三种消费者示例方法
1、最简单的注册。
直接通过channel.basicConsume()设置callback。
2、模拟一个队列消费者,启动线程。
自定义一个消费者,并且将消费者当做一个线程启动。
以上只需要amqp-client依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
3、利用springboot对amqp的支持,通过自定义MessageListener然后,绑定到Container。
这是最常见的做法,自定义监听器,设置消息处理方法。将监听器注入消息监听容器,最后启动容器。
这里需要加入spring-boot-starter-amqp依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
下面给出以上介绍的三种消费者示例代码:
1、这个做法比较原始,也是最容易看懂的。
package com.xxx.huali.hualitest.amqp;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
public class SimpleConsumer {
private static final String host = "192.168.226.100";
private static final String username = "huali";
private static final String password = "huali";
private static final String queue_name1 = "activate";
private static final String exchange_name = "core.down.topic";
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(host);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost("/mec");
//connection
conn = factory.newConnection();
//channel
channel = conn.createChannel();
//queue
channel.queueDeclare(queue_name1, true, false, false, null);
//exchange
channel.exchangeDeclare(exchange_name, "topic",true);
channel.queueBind(queue_name1, exchange_name, "TOPIC.*.*.*.*");
Consumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
BasicProperties properties,byte[] body) throws IOException {
System.out.println("received message -> "+new String(body));
}
};
channel.basicConsume(queue_name1, true, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、这种方式在第一种的基础上,增加了线程,启动线程,设置监听,有点像第三种方式。
package com.xxx.huali.hualitest.amqp;
import java.io.IOException;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownSignalException;
public class QueueConsumer implements Consumer,Runnable{
private static final String host = "192.168.226.100";
private static final String username = "huali";
private static final String password = "huali";
private static final String queue_name1 = "activate";
private static final String exchange_name = "core.down.topic";
Connection conn = null;
Channel channel = null;
public QueueConsumer(){
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(host);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost("/mec");
//connection
conn = factory.newConnection();
//channel
channel = conn.createChannel();
//queue
channel.queueDeclare(queue_name1, true, false, false, null);
//exchange
channel.exchangeDeclare(exchange_name, "topic",true);
channel.queueBind(queue_name1, exchange_name, "TOPIC.*.*.*.*");
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() throws IOException{
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
this.conn.close();
}
@Override
public void run() {
try {
channel.basicConsume(queue_name1, true, this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void handleConsumeOk(String consumerTag) {}
@Override
public void handleCancelOk(String consumerTag) {}
@Override
public void handleCancel(String consumerTag) throws IOException {}
@Override
public void handleDelivery(String consumerTag, Envelope env, BasicProperties props,
byte[] body) throws IOException {
System.out.println("client received msg -> "+new String(body));
}
@Override
public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {}
@Override
public void handleRecoverOk(String consumerTag) {}
}
启动主函数:
package com.xxx.huali.hualitest.amqp;
public class ConsumerMain {
public static void main(String[] args) {
QueueConsumer consumer = new QueueConsumer();
Thread thread = new Thread(consumer);
thread.start();
}
}
3、 第三种方式需要借助spring-amqp的支持。它在实际开发中最常见,在springboot中,ConnectionFactory都直接配置了,连代码都不需要码了,我们只需要配置一个消息监听器就可以了。
package com.xxx.springbootamqp.test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
public class MyMessageListener implements MessageListener{
@Override
public void onMessage(Message message) {
System.out.println("received message -> "+new String(message.getBody()));
}
}
启动主函数:
package com.xxx.springbootamqp.test;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
public class ConsumerMain {
private static final String host = "192.168.226.100";
private static final String username = "huali";
private static final String password = "huali";
private static final String queue_name1 = "activate";
public static void main(String[] args) {
try {
CachingConnectionFactory factory = new CachingConnectionFactory();
factory.setHost(host);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost("/mec");
SimpleMessageListenerContainer container = new
SimpleMessageListenerContainer(factory);
container.setMessageListener(new MyMessageListener());
container.setQueueNames(queue_name1);
container.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码严格来说,并不完整,没有声明队列,交换机,以及队列和交换机的绑定关系,因为消费者一旦绑定了交换机和队列的关系,后面就不需要重复设置了,这个关系就在rabbitmq中形成了,只要不手动删除,他自己不会改变。 因为这是第三个示例,前面的两个示例已经对队列和交换机做了设置和绑定。
这里需要启动容器,类似第二种的启动线程,其实这里也是利用了线程池,算是第二种方式的增强版。而且消费者封装成了BlockingQueueConsumer,更加符合队列消费者的语义。
有一点需要说明的是,这里采用的队列是和交换机进行绑定的,而交换机的类型是主题交换机,消息生产者只需要将消息发送到交换机对应的路由key上即可。也就是routingKey。
channel.basicPublish(exchange_name, "TOPIC.4.128.1.a1OHmhQSyrxMEC_1", null,
message.getBytes());
代码里面的 TOPIC.4.128.1.a1OHmhQSyrxMEC_1就是routingKey。
消费者自己绑定主题交换机和队列,一个消费者,可以申请一个队列,两个消费者申请两个队列,这样,消息就路由到了多个消费者那里,而且互不干扰。
//queue
channel.queueDeclare(queue_name1, true, false, false, null);
//exchange
channel.exchangeDeclare(exchange_name, "topic",true);
channel.queueBind(queue_name1, exchange_name, "TOPIC.*.*.*.*");
最后附上一个简单的生产者代码示例:
package com.xxx.huali.hualitest.amqp;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeoutException;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Publisher {
private static final String host = "192.168.226.100";
private static final String username = "huali";
private static final String password = "huali";
private static final String exchange_name = "core.down.topic";
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(host);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost("/mec");
//connection
conn = factory.newConnection();
//channel
channel = conn.createChannel();
//exchange
channel.exchangeDeclare(exchange_name, "topic",true);
JSONObject object = new JSONObject();
String device_id = "a1OHmhQSyrxMEC_1";
object.put("device_id", device_id);
object.put("opt_type", 128);
JSONObject data = new JSONObject();
data.put("auzcode_devname", device_id);
data.put("regist_result", 1);
long time_stamp = new Date().getTime();
object.put("data", data);
object.put("time_stamp", time_stamp);
String message = object.toJSONString();
//for(int i=0;i<5;i++) {
// System.out.println("i="+i);
channel.basicPublish(exchange_name, "TOPIC.4.128.1.a1OHmhQSyrxMEC_1", null, message.getBytes());
//}
System.out.println("done");
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
channel.close();
conn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
生产者代码很简单,最关键的一句就是channel.basicPublish()那句发送消息代码。它只关心发送到哪个主题,哪个路由上,不关心队列。