建立连接与关闭
我们要启动rabbitmq服务,让后建立连接与关闭。
public static Connection conn() {
ConnectionFactory cf = new ConnectionFactory();
cf.setHost("127.0.0.1");
cf.setPort(5672);
cf.setUsername("guest");
cf.setPassword("guest");
cf.setVirtualHost("/");
//cf.setUri("amqp://guest:guest@127.0.0.1:5672/");
try {
Connection connection = cf.newConnection();
System.out.println("连接RabbitMQ成功");
return connection;
} catch (Exception e) {
e.printStackTrace();
return null;
}
public class RabbitmqUtil {
public static Connection conn() {
ConnectionFactory cf = new ConnectionFactory();
cf.setHost("127.0.0.1");
cf.setPort(5672);
cf.setUsername("guest");
cf.setPassword("guest");
cf.setVirtualHost("/");
//cf.setUri("amqp://guest:guest@127.0.0.1:5672/");
try {
Connection connection = cf.newConnection();
System.out.println("连接RabbitMQ成功");
return connection;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void close(Connection coon) {
if (coon != null) {
try {
coon.close();
System.out.println("断开连接");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
建立连接时要指定必要的信息,也可以用过setUri()建立连接,这里说下setVirtualHost()是设置虚拟主机,它的意思有点像数据库。不同的信道或队列等存放到不同的虚拟主机,不同的虚拟主机之间没有关联。
点击虚拟主机名称可以去设置用户以及其它的。当然你也可以通过命令行去创建。
fanout
该类型的交换器会把所有的消息路由到与其绑定的队列中。无论路由键与绑定键是否比配。
发布消息:
public class Producer {
Connection connection = RabbitmqUtil.conn();
public void send(String msg) {
assert connection != null;
try (Channel channel = connection.createChannel()) {
/**
* 声明一个交换器,其函数有多个,至少必须设置exchange,BuiltinExchangeType参数
* exchange: 交换器名称
* BuiltinExchangeType:交换器类型(枚举类)
* durable:是否持久化,默认false,默认值可以看源码
* autoDelete:自动删除,当没有队列与其绑定则自动删除,一般我们对删除操作比较谨慎,so你懂得,默认false
* internal:内置交换器,客户段无法直接发消息,需要交换器路由到交换器
* args:是个map,存放其它参数
*/
channel.exchangeDeclare("ex1", BuiltinExchangeType.FANOUT, true, false, false, null);
/**
* 声明队列,可以不加参数则表示直接生成一个队列,通过getQueue()获取其名称,但其它参数都是默认,建议使用参数声明
* queue: 队列名
* durable:是否持久化
* exclusive:是否排它,设置为true,则该队列只对本次连接有用,同一连接的不同信道可以访问,不同的连接无法访问
* autoDelete:自动删除,至少有一个消费者连接了该队列,之后所有消费者断开连接则删除
* args:是个map,存放其它参数
*/
channel.queueDeclare("q1", true, false, false, null);
//绑定队列,指明队列、交换器、绑定键、其它参数
channel.queueBind("q1", "ex1", "rk2", null);
/**
* 发布消息
* exchange:发布的交换器名称
* routing key:路由键
* mandatory:后面有单独出一篇,不是本篇重点
* immediate:后面有单独出一篇,不是本篇重点
* BasicProperties:设置属性,比如消息类型,持久化等等,可以new AMQP.BasicProperties().builder()自定义建造也可以使用提供的。
* body:消息,是个字节数组
*/
channel.basicPublish("ex1", "rk1", false, false, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
RabbitmqUtil.close(connection);
}
}
}
消费消息:
public class Consumer {
public void recieve(String consumerTag){
Connection conn = RabbitmqUtil.conn();
assert conn != null;
try (Channel channel=conn.createChannel()){
/**
* 拉模式:主动去获取数据
* queue:获取哪个队列消息的队列名称
* autoAck:是否自动确认,设置为true,消费者收到消息就发送确认,为防止消息丢失设置false,当我们确定已经消费了消息手动确认
*/
/*GetResponse response = channel.basicGet("q1", false);
if (response!=null){
System.out.println(new String(response.getBody()));
//向rabbitmq发送确认接收信息
channel.basicAck(response.getEnvelope().getDeliveryTag(),true);
}else {
System.out.println("not have response");
}*/
/**
* 推模式(消费者订阅),当订阅的队列中有消息时则推送到消费者
* queue:消费的队列名
* autoAck:自动确认消息
* consumerTag: 消费者标签,区分不同的消费者
* nolocal:为true则同个连接中的消费者不能消费生产者的消息
* exclusive: 是否排它
* args:其它参数
* callback: 消费回调
*/
channel.basicConsume("q1", false, consumerTag, false, false, null, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
System.out.println(consumerTag);
System.out.println(envelope.toString());
System.out.println(new String(body));
/**
* 确认消息
* deliverTag:消息投递标记,它表示消费的编号
* multiple: 确认该编号之前的所有消息
*/
channel.basicAck(envelope.getDeliveryTag(), true);
/**
* 拒绝消息
* deliverTag: 投递标记
* requeue: 重入队列,true表示该消息会重新存入队列,已到达再次消费
*/
//channel.basicReject(envelope.getDeliveryTag(),false);
/**
* 批量拒绝消息
* deliverTag: 投递标记
* multiple: 拒绝该编号之前的所有消息
* requeue: 重入队列,true表示该消息会重新存入队列,已到达再次消费
*/
//channel.basicNack(envelope.getDeliveryTag(),true,false);
}
});
Thread.sleep(10*1000);
}catch (Exception e){
e.printStackTrace();
}finally {
RabbitmqUtil.close(conn);
}
}
}
运行
public class RabbitmqTest {
@Test
public void test(){
new Producer().send("hello world!");
new Consumer().recieve("consumer1");
}
}
结果:
因为前面没有讲过代码实现,所以在这里讲的详细点,各行代码以及方法和参数我都有解释。上述消费者拉模式及推模式以及消息的确认与拒绝我都有写出来。还要注意不能用循环或定时使用拉模式代替推模式,会降低性能,还会增加复杂度。
现在说先说下fanout类型。我们发现上述的路由键是rk1,但绑定键是rk2,消费者依旧消费了消息,则证明了会把所有的消息路由到与其绑定的队列中。
direct
direct类型则是当路由键和绑定键完全匹配时才会存消息到队列,比较简单,可以把上边的代码稍微改下就行,不演示了。
topic
我们发现direct类型的交换器必须路由键和绑定键完全匹配,这种匹配方式的局限性太大了。所以出现topic这种模糊匹配的交换器类型。
- 路由键与绑定键单词之间用"."分割比如(aaa.bbb.ccc)
- 绑定键的使用"#"匹配多个单词(可以是0个)比如(aaa.#可以匹配路由键是aaa.bbb.ccc和aaa的)
- 绑定键的使用"*"匹配一个单词比如(aaa.*匹配路由键是aaa.bbb、aaa.ccc或aaa但是不匹配aaa.bbb.ccc)。
headers
headers类型不需要关心路由键和绑定键的规则,我们需要在生产者发布消息的时候通过AMQP.BasicProperties.Builder()设置headers属性,headers是一个map键值对。然后当绑定队列时设置第四个参数,其也是一个map,当键值对匹配时则存消息到queue。我们可以在map中存放key为x-match,值为all则表示全部匹配正确,any表示有一个匹配正确即可。当我们不设置x-match时是表示全部匹配。
public class HeaderProducer {
Connection connection = RabbitmqUtil.conn();
public void send(String msg) {
assert connection != null;
try (Channel channel = connection.createChannel()) {
channel.exchangeDeclare("ex3", BuiltinExchangeType.HEADERS, true, false, false, null);
channel.queueDeclare("q3", true, false, false, null);
Map<String,Object> args=new HashMap<>();
args.put("x-match","all");
args.put("client","app");
args.put("role","admin");
channel.queueBind("q3", "ex3", "", args);
Map<String,Object> headers=new HashMap<>();
headers.put("client","app");
headers.put("role","admin");
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder().headers(headers).build();
channel.basicPublish("ex3", "", false, false, basicProperties, msg.getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
RabbitmqUtil.close(connection);
}
}