依赖
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
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() { return new Queue(TopicRabbitConfig.MAN, true, false, false); }
@Bean(WOMAN) public Queue womanQueue() { return new Queue(TopicRabbitConfig.WOMAN); }
@Bean(EXCHANGE) TopicExchange exchange() { return new TopicExchange(EXCHANGE, true, false); }
@Bean Binding bindingExchangeMessage(@Qualifier(MAN) Queue queue, @Qualifier(EXCHANGE) TopicExchange exchange) { return BindingBuilder.bind(queue).to(exchange).with(MAN); }
@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); 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 { 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);
- 参数一:签收消息的编号
- 参数二:是否批量签收,比如签收了编号为4的消息,但是前面的0,1,2,3 都没有签收,则MQ若是批量的签收,它会把0,1,2,3 都签收
channel.basicReject(tag, false);
- 参数一:签收消息的编号
- 参数二:失败后消息不重新放回队列
@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放在类上不可行