文章目录
如何在Spring中使用RabbitMQ?
RabbitMQ相关概念介绍
RabbitMQ示意图。
当消息抵达RabbitMQ代理时,它会进入为其设置的Exchange上。Exchange负责将它路由到一个或多个队列中,这个过程会根据Exchange的类型,Exchange和队列之间的binding以及消息的routing key进行路由。
有多种不同的Exchange
-
Default:
它会将消息路由至名字与消息routing key相同的队列
,所有的队列都会自动绑定至Default Exchange -
Direct:如果
消息的routing key与队列的binging key相同
,那么消息就会路由到该队列上 -
Topic:如果
消息的routing key与队列的binging key(可能包含通配符)匹配
,那么消息会被路由到一个或多个这样的队列上 -
Fanout:不管routing key和binging key是什么,
消息都将会路由到所有绑定队列
上。 -
Headers:与Topic Exchange类似,只不过要
基于消息的头消息进行路由
,而不是routing key。 -
Dead letter:捕获所有无法投递(也就是无法匹配所有已定义的Exchange和队列的binging关系)的消息
添加依赖
添加AMQP starter到构建文件中以后,将会触发Spring Boot的自动配置功能,这样会为我们创建一个AMQP连接工厂和RabbitTemplate Bean
。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
在使用RabbitMQ的时候,我们需要关注RabbitMQ的相关属性配置
RabbitMQ相关属性配置
属性 | 描述 |
---|---|
spring.rabbitmq.addresses | 一个逗号分隔的 RabbitMQ Broker 地址列表 |
spring.rabbitmq.host | Broker 主机(默认为 localhost) |
spring.rabbitmq.port | Broker 端口(默认为 5672) |
spring.rabbitmq.username | 访问 Broker 的用户名(可选) |
spring.rabbitmq.password | 访问 Broker 的密码(可选) |
在本机运行时这些属性是没有太大用处的,本机默认会监听5672端口。
部署到生产环境时,可以这么配置
spring:
profiles: prod
rabbitmq:
host: rabbit.tacocloud.com
port: 5673
username: user
password: password
默认的exchange
和routing-key
都是""(空字符串)
,可以进行相应的配置来进行设置。
spring:
rabbitmq:
template:
exchange: tacocloud.orders
routing-key: kitchens.central
使用RabbitMQ发送消息
RabbitTemplate中发送消息的方法
Spring对RabbitMQ的支持的核心是RabbitTemplate
。RabbitTemplate
与JmsTemplate
类似
发送消息时主要使用两类方法send(...)
和sendAndConvert(...)
// 发送原始消息
//routingKey就相当于是指定了消息发送的目的地址一样
void send(Message message) throws AmqpException;
void send(String routingKey, Message message) throws AmqpException;
//若未指明消息发送的Exchange则会发送到默认的Exchange,默认的Exchange可以到配置文件中进行设置
void send(String exchange, String routingKey, Message message) throws AmqpException;
// 发送从对象转换过来的消息
void convertAndSend(Object message) throws AmqpException;
void convertAndSend(String routingKey, Object message) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException;
// 发送经过处理后从对象转换过来的消息,并且再通过MessagePostProcessor添加对消息的处理
void convertAndSend(Object message, MessagePostProcessor mPP) throws AmqpException;
void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException;
如下的代码使用send(String routingKey, Message message)
方法来发送消息,该send方法传入的参数是Message对象,默认使用的是消息转换器是SimpleMessageConverter
(消息转换器用来将要发送的对象与Message对象进行转化,不管我们传入的参数是什么,最后都会转化为Message对象来进行传输)。由于需要将JSON和Message进行转换,所以下面将会讲解如何使用Jackson2JsonMessageConverter
。
private RabbitTemplate rabbit;
@Autowired
public RabbitOrderMessagingService(RabbitTemplate rabbit) {
this.rabbit = rabbit;
}
@Override
public void sendOrder(Order order) {
//org.springframework.amqp.support.converter.MessageConverter下的MessageConverter
MessageConverter converter = rabbit.getMessageConverter();
//org.springframework.amqp.core.MessageProperties下的MessageProperties
MessageProperties props = new MessageProperties();
Message message = converter.toMessage(order, props);
rabbit.send("tacocloud.order.queue", message);
}
配置消息转换器
转化JSON和Message使用Jackson2JsonMessageConverter
只需要配置一个Jackson2JsonMessageConverter
的Bean即可得到一个MessageConverter
的Bean
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
最终会发现Jackson2JsonMessageConverter
继承自MessageConverter
,所以才会得到一个MessageConverter
的bean
public class Jackson2JsonMessageConverter extends AbstractJackson2MessageConverter {
//......
}
public abstract class AbstractJackson2MessageConverter extends AbstractMessageConverter
implements BeanClassLoaderAware, SmartMessageConverter {
//......
}
public abstract class AbstractMessageConverter implements MessageConverter {
//......
}
在RabbitTemplate
中默认是有一个MessageConverter
转换器的,不过是默认使用的是SimpleMessageConverter
。
private MessageConverter messageConverter = new SimpleMessageConverter();
如果我们需要替代这个MessageConverter
,只需要像上面显示声明一个MessageConverter
类型的的Bean即可。
设置消息属性
如果我们想要对发送的消息额外增加相应的属性,则应该使用如下的方法来发送消息。
使用void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor)
发送消息并添加对消息的处理 。
在postProcessor
中,从Message
中获得MessageProperties
对象,然后通过setHeader
方法设置"X_ORDER_SOURCE"
头信息。
public void sendOrder(Order order) {
rabbit.convertAndSend("tacocloud.order.queue", order,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message)
throws AmqpException {
MessageProperties props = message.getMessageProperties();
props.setHeader("X_ORDER_SOURCE", "WEB");//为消息增加一个X_ORDER_SOURCE字段
return message;
}
});
}
接收来自RabbitMQ的消息
接收来自RabbitMQ的消息与使用JMS也很相似,有两种方案:
- 使用
RabbitTemplate
从队列拉取消息。采用该模式,在代码中请求消息时该线程会一直阻塞在那里,直到等待着消息到达为止。 - 将消息推送至带有
@RabbitListener
注解的方法。采用这种方式时,线程不会一直阻塞在原地。而是在消息到达时开始执行相应的处理逻辑,在消息没有到达时该线程可以执行其它任务,这样就不会造成线程阻塞。
RabbitTemplate中接收消息的方法
接收消息时主要使用如下方法
// 接收消息
Message receive() throws AmqpException;
Message receive(String queueName) throws AmqpException;
Message receive(long timeoutMillis) throws AmqpException;
//long类型的参数用来指定接收消息的超时时间,默认情况下接收消息的超时时间为0毫秒,也就是说调用receive方法就会立即返回结果,如果没有可用消息,则返回null。
Message receive(String queueName, long timeoutMillis) throws AmqpException;
// 接收从消息转换过来的对象
Object receiveAndConvert() throws AmqpException;
Object receiveAndConvert(String queueName) throws AmqpException;
Object receiveAndConvert(long timeoutMillis) throws AmqpException;
Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException;
// 接收从消息转换过来的类型安全的对象
<T> T receiveAndConvert(ParameterizedTypeReference<T> type) throws AmqpException;
<T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException;
<T> T receiveAndConvert(long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException;
<T> T receiveAndConvert(String queueName, long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException;
receive(...)
方法对应着send(…)方法,用于接收来自队列的原始Message对象,并返回Message对象
receiveAndConvert(...)
对应着sendAndConvert(…)方法,用于接收Message对象,并返回一个Object。
示例:
使用Message receive(String queueName, long timeoutMillis)
方法接收消息
public Order receiveOrder() {
MessageConverter converter = rabbit.getMessageConverter();
Message message = rabbit.receive("tacocloud.order.queue", 20000);
return message != null
? (Order) converter.fromMessage(message)
: null;
}
使用Object receiveAndConvert(String queueName)
方法接收消息
public Order receiveOrder() {
return (Order) rabbit.receiveAndConvert("tacocloud.order.queue");
}
使用<T> T receiveAndConvert(String queueName, long timeoutMillis, ParameterizedTypeReference<T> type)
接收消息
public Order receiveOrder() {
return rabbit.receiveAndConvert("tacocloud.order.queue", new ParameterizedTypeReference<Order>() {});
}
使用 receiveAndConvert() 的ParameterizedTypeReference
的惟一要求是消息转换器必须是 SmartMessageConverter
的实现;Jackson2JsonMessageConverter
是唯一可以选择的开箱即用的实现。因此如果采用这种方法得声明一个Jackson2JsonMessageConverter
的Bean。
使用监听器来处理RabbitMQ的消息
@RabbitListener
与@JmsLinstener
及其类似,不过一个是用来处理来自RabbitMQ代理的消息,一个是处理来自JMS代理的消息。两者都是被动地接收消息,并不是主动地去请求消息。
使用@RabbitListener
注解可以实现消息驱动的RabbitMQ的bean。当消息到达队列时,声明了该注解的方法就会被自动调用。声明了该注解的方法不会被你的应用程序显示调用,当消息到达时,Spring会自动调用该方法,并会将消息中传递的对象作为参数。
比如说我在send(…)方法中传递的是一个Order对象
public void sendOrder(Order order) {
rabbit.convertAndSend("tacocloud.order.queue", order,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message)
throws AmqpException {
MessageProperties props = message.getMessageProperties();
props.setHeader("X_ORDER_SOURCE", "WEB");
return message;
}
});
}
那么使用@RabbitListener
接收时就可以这么接收。
@Profile("rabbitmq-listener")
@Component
public class OrderListener {
//......
@RabbitListener(queues = "tacocloud.order.queue")
public void receiveOrder(Order order) {
//do sth
}
//......
}
参考
《Spring 实战》第五版,作者:Craig Walls