Spring异步通信(二)RabbitMQ 和 AMQP
前面讲到JMS消息使用目的地名称来寻址,接受者要从这里检索信息
而AMQP消息使用Exchange 和 routing key 来寻址。消息就与接受者要监听的队列解耦了。
发送到RabbitMQ Exchange的消息会基于routing key 和 binding被路由到一个或多个队列上
当消息抵达RabbitMQ代理的时候,它会进入为其设置Exchange上。
Exchange负责将它路由到一个或多个队列上。这个过程会根据Exchange的类型、Exchange和队列之间的binding以及消息的routing key进行路由
- Default:这是代理创建的特殊Exchange。它会将信心路由至名字与消息routing key 相同的队列上。所有的队列都会自动绑定至Default Exchange。
- Direct: 如果消息的routing key 与队列的binding key 相同,那么消息将会路由到该队列上。
- Topic: 如果消息的routing key 与队列的binding key (可能会包含通配符)匹配,那么消息将会路由到一个或多个这样的队列上。
- Fanout: 不管routing key 和 binding key 是什么,消息都将会路由到所有绑定队列上。
- Headers:与Topic Exchange 类似,只不过要基于消息的头信息进行路由,而不是routing key
- Dead letter: 捕获所有无法投递(也就是他们无法匹配所有已定义的Exchange和队列的routing关系)的信息
- 最简单的Exchange形式是Default 和 Fanout 因为他们大致对应了JMS中的队列和主题。
一、 搭建RabbitMQ环境
增加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
属性 | 描述 |
---|---|
spring.rabbitmq.addresses | 逗号分隔的RabbitMQ代表地址列表 |
spring.rabbitmq.host | 代理的主机(默认为localhost) |
spring.rabbitmq.port | 代理的端口(默认为5672) |
spring.rabbitmq.username | 访问代理所使用的用户名(可选) |
spring.rabbitmq.password | 访问代理所使用的密码(可选) |
spring:
profiles: prod
rabbitmq:
host: rabbit.tacocloud.com
port: 5673
username: tacoweb
password: 13tm31n
二、 通过RabbitTemplate发送信息
与JmsTemplate不同,RabbitTemplate会按照Exchanges 和 routing key 来发送信息。
//发送原始信息
public void send(Message message) throws AmqpException {
this.send(this.exchange, this.routingKey, message);
}
public void send(String routingKey, Message message) throws AmqpException {
this.send(this.exchange, routingKey, message);
}
public void send(String exchange, String routingKey, Message message) throws AmqpException {
this.send(exchange, routingKey, message, (CorrelationData)null);
}
//发送根据对象转换而成的信息
public void convertAndSend(Object object) throws AmqpException {
this.convertAndSend(this.exchange, this.routingKey, object, (CorrelationData)null);
}
public void correlationConvertAndSend(Object object, CorrelationData correlationData) throws AmqpException {
this.convertAndSend(this.exchange, this.routingKey, object, correlationData);
}
public void convertAndSend(String routingKey, Object object) throws AmqpException {
this.convertAndSend(this.exchange, routingKey, object, (CorrelationData)null);
}
//发送根据对象转换而成的信息并且带有后期处理的功能
public void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException {
this.convertAndSend(this.exchange, this.routingKey, message, messagePostProcessor);
}
public void convertAndSend(String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException {
this.convertAndSend(this.exchange, routingKey, message, messagePostProcessor, (CorrelationData)null);
}
public void convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor) throws AmqpException {
this.convertAndSend(exchange, routingKey, message, messagePostProcessor, (CorrelationData)null);
}
多了指定Exchange 和routing key 不想Jms 只需要接受目的地名称Destination
没有Exchange 默认发送Default Exchange
没有routing key 会把消息路由至默认的routing key
例子:
public interface OrderMessagingService {
void sendOrder(Order order);
}
@Service
public class RabbitOrderMessagingService
implements OrderMessagingService {
private RabbitTemplate rabbit;
@Autowired
public RabbitOrderMessagingService(RabbitTemplate rabbit) {
this.rabbit = rabbit;
}
public void sendOrder(Order order) {
// 方式一
//这里使用默认的Exchange 名字是""(空的String) 可以在yml 改写这些默认的配置
/*
spring:
rabbitmq:
template:
exchange: tacocloud.orders
routing-key: kitchens.central 没有指定routing-key 使用该值
*/
MessageConverter converter = rabbit.getMessageConverter();//消息转换器
MessageProperties props = new MessageProperties();//提供消息属性
Message message = converter.toMessage(order, props);
// routing key
rabbit.send("tacocloud.order",message);
//方式二
//routingKey
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;
}
});
}
}
三、 配置消息转换器
默认情况下,消息转换是通过SimpleMessageConverter来实现。它能将简单类型 如 String 和 Serializable 对象转换成Message对象。
以下有几个消息转换器
- Jackson2JsonMessageConverter:使用Jackson 2 Json 实现对象和Json的相互转换。
- MarshallingMessageConverter: 使用Spring的Marshaller 和 Unmarshaller 进行转换。
- SerializerMessageConverter:使用Spring的Serializer和Deserializer转换String和任意种类的原生对象。
- SimpleMessageConverter:转换String、字符数组和Serializable类型。
- ContentTypeDelegatingMessageConverter:基于contenType头信息、将转换功能委托给另一个MessageConverter。
- MessageMessageConverter:将消息转换功能委托给另外一个MessageConverter,并将头信息的转换委托给AMQPHeaderConverter。
//注册到bea里面 默认的消息转换器
@Configuration
public class MessagingConfig {
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
四、 设置消息属性
比如增加一些头信息
public void sendOrder(Order order) {
MessageConverter converter = rabbit.getMessageConverter();
MessageProperties props = new MessageProperties();
props.setHeader("X_ORDER_SOURCE","WEB");
Message message = converter.toMessage(order, props);
// routing key
rabbit.send("tacocloud.order",message);
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;
}
});
}
五、 接受来自RabbitMQ的信息
与JMS相似
- 使用RabbitTemplate从队列拉去信息
- 将消息推送至带有@RabbitListener注解的方法
使用RabbitTemplate接受信息
//接受信息
public Message receive() throws AmqpException {
String queue = this.getRequiredQueue();
return this.receive(queue);
}
public Message receive(String queueName) {
return this.receiveTimeout == 0L ? this.doReceiveNoWait(queueName) : this.receive(queueName, this.receiveTimeout);
}
public Message receive(long timeoutMillis) throws AmqpException {
String queue = this.getRequiredQueue();
return timeoutMillis == 0L ? this.doReceiveNoWait(queue) : this.receive(queue, timeoutMillis);
}
public Message receive(String queueName, long timeoutMillis) {
Message message = (Message)this.execute((channel) -> {
Delivery delivery = this.consumeDelivery(channel, queueName, timeoutMillis);
if (delivery == null) {
return null;
} else {
if (this.isChannelLocallyTransacted(channel)) {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
channel.txCommit();
} else if (this.isChannelTransacted()) {
ConnectionFactoryUtils.registerDeliveryTag(this.getConnectionFactory(), channel, delivery.getEnvelope().getDeliveryTag());
} else {
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
return this.buildMessageFromDelivery(delivery);
}
});
this.logReceived(message);
return message;
}
//接收由消息转换而成的对象
public Object receiveAndConvert() throws AmqpException {
return this.receiveAndConvert(this.getRequiredQueue());
}
public Object receiveAndConvert(String queueName) throws AmqpException {
return this.receiveAndConvert(queueName, this.receiveTimeout);
}
public Object receiveAndConvert(long timeoutMillis) throws AmqpException {
return this.receiveAndConvert(this.getRequiredQueue(), timeoutMillis);
}
public Object receiveAndConvert(String queueName, long timeoutMillis) throws AmqpException {
Message response = timeoutMillis == 0L ? this.doReceiveNoWait(queueName) : this.receive(queueName, timeoutMillis);
return response != null ? this.getRequiredMessageConverter().fromMessage(response) : null;
}
//接收由消息转换而成的类型安全的对象
public <T> T receiveAndConvert(ParameterizedTypeReference<T> type) throws AmqpException {
return this.receiveAndConvert(this.getRequiredQueue(), type);
}
public <T> T receiveAndConvert(String queueName, ParameterizedTypeReference<T> type) throws AmqpException {
return this.receiveAndConvert(queueName, this.receiveTimeout, type);
}
public <T> T receiveAndConvert(long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException {
return this.receiveAndConvert(this.getRequiredQueue(), timeoutMillis, type);
}
public <T> T receiveAndConvert(String queueName, long timeoutMillis, ParameterizedTypeReference<T> type) throws AmqpException {
Message response = timeoutMillis == 0L ? this.doReceiveNoWait(queueName) : this.receive(queueName, timeoutMillis);
return response != null ? this.getRequiredSmartMessageConverter().fromMessage(response, type) : null;
}
send()用于发送原始的Message对象 而 Receive()会接受来自队列的原始Message对象
receiveAndConvert()接受消息并且在返回之前使用一个消息转换器将他们转换为领域对象。
首先这些方法都不会接受Exchange和routing key作为参数。
这是因为Exchange和routing key 是用来将消息路由至队列的,在消息位于队列之后,他们的目的地是将它们从队列中拉取下来的消费者。消费消息的应用本身并不需要关心Exchange 和 routing key。消费消息的应用只需要知道队列信息就可以了。
long 类型的参数 是用来指定接收消息的超时时间。
默认0毫秒,也就是说,调用receive()会立即返回,如果没有返回可用消息,则返回null。
这个与JMS的receive()有显著差异。
例子:
public Order receiveOrder() {
Message message = rabbit.receive("tacocloud.orders");
return message!=null?(Order)converter.fromMessage(message):null;
}
public Order receiveOrder2() {
Message message = rabbit.receive("tacocloud.order.queue",30000);
return message!=null?(Order)converter.fromMessage(message):null;
}
也可以在yaml指定超时时间
spring:
rabbitmq:
template:
receive-timeout: 30000
例子:
@Profile("rabbitmq-template")
@Component("templateOrderReceiver")
public class RabbitOrderReceiver implements OrderReceiver {
private RabbitTemplate rabbit;
public RabbitOrderReceiver(RabbitTemplate rabbit) {
this.rabbit = rabbit;
}
public Order receiveOrder() {
return (Order) rabbit.receiveAndConvert("tacocloud.order.queue");
}
}
六、 使用监听器处理RabbitMQ的消息
@RabbitListener
@Profile("rabbitmq-listener")
@Component
public class OrderListener {
private KitchenUI ui;
@Autowired
public OrderListener(KitchenUI ui) {
this.ui = ui;
}
@RabbitListener(queues = "tacocloud.order.queue")
public void receiveOrder(Order order) {
ui.displayOrder(order);
}
}
与@JmsListener 差不多
引用于Spring实战5