依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

生产者

配置

1
2
3
4
5
6
server.port=8091

#这里容易和15672搞混 15672是rabbitmq web管理插件的http端口
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

配置类

这个类主要用于配置rabbitmq的交换机、队列和路由绑定,不一定需要配置在生产者端,可以单独实现一个配置端,也可以直接在rabbitmq端配置好而不需要额外的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Configuration
public class TopicRabbitConfig {
//绑定键
private final static String MAN = "topic.man";

private final static String WOMAN = "topic.woman";

private final static String EXCHANGE = "topicExchange";

@Bean(MAN)
public Queue manQueue() {
// name:队列名称
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,代表是否独占,如果未true,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
return new Queue(TopicRabbitConfig.MAN, true, false, false);
}

@Bean(WOMAN)
public Queue womanQueue() {
return new Queue(TopicRabbitConfig.WOMAN);
}

@Bean(EXCHANGE)
TopicExchange exchange() {
// name:交换机名称
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
return new TopicExchange(EXCHANGE, true, false);
}


//#匹配零个或多个单词,*匹配单个单词
//将firstQueue和topicExchange绑定,而且绑定的键值为topic.man
//这样只要是消息携带的路由键是topic.man,才会分发到该队列
@Bean
Binding bindingExchangeMessage(@Qualifier(MAN) Queue queue, @Qualifier(EXCHANGE) TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(MAN);
}

//#匹配零个或多个单词,*匹配单个单词
//将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
//这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
@Bean
Binding bindingExchangeMessage2(@Qualifier(WOMAN) Queue queue, @Qualifier(EXCHANGE) TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("topic.#");
}

}

发送消息类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
public class TopicSender {

@Autowired
private RabbitTemplate rabbitTemplate;

@GetMapping("/send")
public void send(String messageStr, String routingKey){
String messageId = String.valueOf(UUID.randomUUID());
String messageData = "test message, hello!";
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
map.put("sex","man");
map.put("message", messageStr);
String message = JSON.toJSONString(map);
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("topicExchange", routingKey, message);
}
}

消费者

配置

1
2
3
4
5
6
7
8
server.port=8092
spring.application.name=rabbit-consumer
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

#开启手动签收
spring.rabbitmq.listener.simple.acknowledge-mode=manual

监听类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class TopicManReceiver {

private Logger log = LoggerFactory.getLogger(TopicManReceiver.class);

@RabbitListener(queues = "topic.man")
public void processMan(Message message, Channel channel) {
log.info("man message is {}", message);
try {
//参数一:签收消息的编号 参数二:是否批量签收,比如签收了编号为4的消息,但是前面的0,1,2,3 都没有签收,则MQ若是批量的签收,它会把0,1,2,3 都签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}

@RabbitListener(queues = "topic.woman")
public void processWoman(Message message, Channel channel) {
log.info("woman message is {}", message);
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (IOException e) {
e.printStackTrace();
}
}
}

测试

用浏览器访问http://127.0.0.1:8091/send?message=132465&routingKey=topic.man

可以看到processMan与processWoman方法同时被触发,这是因为topicExchange与topic.woman队列的绑定键是topic.#,所以topicExchange所有路由键以topic.开头的消息都会被发送到topic.woman队列。
关于路由键,举个小例子
队列Q1 绑定键为 .TT.          队列Q2绑定键为  TT.#
如果一条消息携带的路由键为 A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;
用浏览器访问http://127.0.0.1:8091/send?message=132465&routingKey=topic.woman

我们可以看到只有processWoman被触发,因为消息不会被发送到topic.man队列上了
注:若队列中的消息没有被消费者消费,则消息会暂存在队列中直到由消费者消费,所以存在消息堆积的情况。上述代码中,设置了手动签收
关于手动签收方法:
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

  1. 参数一:签收消息的编号
  2. 参数二:是否批量签收,比如签收了编号为4的消息,但是前面的0,1,2,3 都没有签收,则MQ若是批量的签收,它会把0,1,2,3 都签收

channel.basicReject(tag, false);

  1. 参数一:签收消息的编号
  2. 参数二:失败后消息不重新放回队列

@RabbitListener

当@RabbitListener放在类上时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@RabbitListener(queues = "topic.woman")
public class TopicWomanReceiver {

private Logger log = LoggerFactory.getLogger(TopicWomanReceiver.class);

@RabbitHandler
public String process(Message message, Channel channel) {
log.info("woman message is {}", message);
try {
channel.basicAck((Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG), false);
} catch (IOException e) {
e.printStackTrace();
}
return "hello world,too";
}
}

生产者发送消息

可以看到,因为监听方法接受的参数类型为Message,程序报了无法处理String的错误,但是之前@RabbitListener放在方法上时,监听器是可以处理String类型的消息的,在手动签收的机制下,使用Message是必须的,所以手动签收下将@RabbitListener放在类上不可行


本站总访问量:次本站总访问数:人
本站由 Peng Tong 使用 Stellar 1.29.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。