第六节 SpringBoot集成RabbitMQ综合运用(SSM框架集成RabbitMQ)
先展示当前项目的效果
RabbitMQ一般不会单独使用。今天分享的是如何在基于SpringBoot的SSM框架中 集成RabbitMQ。
点击下方图片,可以查看清晰效果图
核心配置
(1)队列、Exchanger路由器交换机、路由键的名字由SpringBoot的配置文件维护。使用注解注入配置中的值。@Value("${queue.name")
(2)集成RabbitMQ的application.yml的配置。可配置RabbitMQ的服务器的地址,接收端每次获取到的消息的数量等。
(3)使用@Configuration来创建队列、路由器,并设置它们的绑定关系。定义消息传输格式为JSON。
(4)创建发送端,注入进RabbitTemplate这个类,用于发消息。
(5)最后创建接收端,用于接收消息。根据配置,手动确认消息或者自动确认消息给RabbitMQ服务器。
一、队列、交换机的名字
在application.yml中配置队列、路由器、路由键的名字。
#自定义参数
defineProps:
rabbit: #MQ队列名称
direct:
exchange: local::mq06:exchange:e01
routing.key:
beauty: mq06::routeKey_love_beauty
stock: mq06::routeKey_love_stock
food: mq06::routeKey_love_food
queue:
queue01: local::mq06:queue:q01
queue02: local::mq06:queue:q02
二、配置RabbitMQ服务器地址
spring:
datasource:
url: jdbc:mysql://localhost:3306/basessm
driverClassName: com.mysql.jdbc.Driver
password: 3333
username: root
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirms: false #是否开启发送确认
publisher-returns: true #是否开启发送失败退回
listener:
#SimpleMessageListenerContainer专门用于配置并发量
simple:
concurrency: 10 #消费者数量
max-concurrency: 10 #最大消费者数量
prefetch: 1 #限流(消费者每次从队列获取的消息数量)
auto-startup: true #启动时自动启动容器
acknowledge-mode: manual #开启ACK手动确认模式
三、创建队列路由器及其绑定
package com.safesoft.ssm.rabbitconfig;
import lombok.Getter;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author jay.zhou
* @date 2019/4/25
* @time 10:27
*/
@Configuration
@Getter
public class RabbitMQConfig {
/**
* 交换机名称
*/
@Value("${defineProps.rabbit.direct.exchange}")
private String directExchange;
/**
* 队列一名称,只对美女感兴趣
*/
@Value("${defineProps.rabbit.direct.queue.queue01}")
private String queue01;
/**
* 队列二名称,对股票和美食感兴趣
*/
@Value("${defineProps.rabbit.direct.queue.queue02}")
private String queue02;
/**
* 路由键(美女)
*/
@Value("${defineProps.rabbit.direct.routing.key.beauty}")
private String beautyRoutingKey;
/**
* 路由键(美食)
*/
@Value("${defineProps.rabbit.direct.routing.key.food}")
private String foodRoutingKey;
/**
* 路由键(股票)
*/
@Value("${defineProps.rabbit.direct.routing.key.stock}")
private String stockRoutingKey;
/**
* 定义交换器
* 三个参数解释如下
* name:交换机名称
* durable:是否持久化,true表示交换机会被写入磁盘,即使RabbitMQ服务器宕机,也能恢复此交换机
* autoDelete:表示消息交换机没有在使用时将被自动删除 默认是false
*
* @return DirectExchange
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange(directExchange, true, false);
}
/**
* 定义队列01
* 第一个参数是queue:要创建的队列名
* 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复队列
* 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
* 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
* 第五个参数是arguments:其它参数
*/
@Bean
public Queue queue01() {
return new Queue(queue01, true, false, false, null);
}
/**
* 定义队列02
* 参数参考队列01
*/
@Bean
public Queue queue02() {
return new Queue(queue02, true, false, false, null);
}
/**
* 绑定-将队列绑定到路由器上,队列告诉路由器它感兴趣的话题(路由键)
*
* @param queue01 队列01
* @param directExchange 交换器
* @return Binding
*/
@Bean
public Binding queue01Binding(Queue queue01, DirectExchange directExchange) {
//队列一绑定到路由器上,并告诉路由器,关于美女的消息,发送给它
return BindingBuilder.bind(queue01).to(directExchange).with(beautyRoutingKey);
}
/**
* 绑定-将队列绑定到路由器上,队列告诉路由器它感兴趣的话题(路由键)
*
* @param queue02 队列02
* @param directExchange 交换器
* @return Binding
*/
@Bean
public Binding queue02FoodBinding(Queue queue02, DirectExchange directExchange) {
//队列二绑定到路由器上,并告诉路由器,关于美食的消息,发送给它
return BindingBuilder.bind(queue02).to(directExchange).with(foodRoutingKey);
}
@Bean
public Binding queue02StockBinding(Queue queue02, DirectExchange directExchange) {
//队列二绑定到路由器上,并告诉路由器,关于股票的消息,发送给它
return BindingBuilder.bind(queue02).to(directExchange).with(stockRoutingKey);
}
/**
* 定义消息转换实例 ,转化成 JSON传输
*
* @return Jackson2JsonMessageConverter
*/
@Bean
public MessageConverter integrationEventMessageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* 配置启用rabbitmq事务
*
* @param connectionFactory connectionFactory
* @return RabbitTransactionManager
*/
@Bean
public RabbitTransactionManager rabbitTransactionManager(CachingConnectionFactory connectionFactory) {
return new RabbitTransactionManager(connectionFactory);
}
}
四、创建发送端,注入进RabbitTemplate
package com.safesoft.ssm.rabbitMQ;
import com.safesoft.ssm.rabbitconfig.RabbitMQConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
/**
* @author jay.zhou
* @date 2019/4/25
* @time 10:54
*/
@Component
public class RabbitMQSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQSender.class);
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RabbitMQConfig rabbitMQConfig;
@PostConstruct
private void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setChannelTransacted(true);
}
@Transactional
public Boolean sendMessageToQueue01AboutBeauty(Object msg) {
rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getBeautyRoutingKey(), msg);
return Boolean.TRUE;
}
@Transactional
public Boolean sendMessageToQueue02AboutFood(Object msg) {
rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getStockRoutingKey(), msg);
return Boolean.TRUE;
}
@Transactional
public Boolean sendMessageToQueue02AboutStock(Object msg) {
rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getFoodRoutingKey(), msg);
return Boolean.TRUE;
}
/**
* 消息发送到交换器Exchange后触发回调。
* 使用该功能需要开启确认,spring-boot中配置如下:
* spring.rabbitmq.publisher-confirms = true
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
LOGGER.info("消息已确认 cause:{} - {}", cause, correlationData);
} else {
LOGGER.error("消息未确认 cause:{} - {}", cause, correlationData);
}
}
/**
* 通过实现ReturnCallback接口,
* 如果消息从交换器发送到对应队列失败时触发
* 比如根据发送消息时指定的routingKey找不到队列时会触发
* 使用该功能需要开启确认,spring-boot中配置如下:
* spring.rabbitmq.publisher-returns = true
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
LOGGER.error("消息被退回:{}", message);
LOGGER.error("消息使用的交换机:{}", exchange);
LOGGER.error("消息使用的路由键:{}", routingKey);
LOGGER.error("描述:{}", replyText);
}
}
五、创建接收端
package com.safesoft.ssm.rabbitMQ;
import com.rabbitmq.client.Channel;
import com.safesoft.ssm.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author jay.zhou
* @date 2019/4/25
* @time 11:07
* 监听队列01的消息,关于美女的消息,此队列都感兴趣
*/
@Component
@RabbitListener(queues = {"${defineProps.rabbit.direct.queue.queue01}"})
public class BeautyListener {
private static final Logger LOGGER = LoggerFactory.getLogger(BeautyListener.class);
@RabbitHandler
public void receiver(@Payload UserEntity msg, @Headers Channel channel, Message message) throws IOException {
LOGGER.info("接收到的消息:{}", msg);
try {
// 确认消息已经消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
LOGGER.info("确认处理消息:{}", msg);
} catch (IOException e) {
LOGGER.error("消费处理异常:{} - {}", msg, e);
// 拒绝当前消息,并把消息返回原队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
package com.safesoft.ssm.rabbitMQ;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @author jay.zhou
* @date 2019/4/25
* @time 11:07
* 监听队列02的消息,关于美食和股票的消息,此消费者都感兴趣
*/
@Component
@RabbitListener(queues = {"${defineProps.rabbit.direct.queue.queue02}"})
public class FoodListener {
private static final Logger LOGGER = LoggerFactory.getLogger(FoodListener.class);
@RabbitHandler
public void receiver(@Payload String msgAboutFoodOrStock, @Headers Channel channel, Message message) throws IOException {
LOGGER.info("接收到的消息:{}", msgAboutFoodOrStock);
try {
// 确认消息已经消费成功
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
LOGGER.info("确认处理消息:{}", msgAboutFoodOrStock);
} catch (IOException e) {
LOGGER.error("消费处理异常:{} - {}", msgAboutFoodOrStock, e);
// 拒绝当前消息,并把消息返回原队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}
}
源代码下载
源代码地址:https://github.com/hairdryre/Study_RabbitMQ
下一篇:第七节 用户商城抢单并发实战
阅读更多:从头开始学RabbimtMQ目录贴