抛砖引玉,如果有更好建议,请提给我~
目录
1.pom:依赖自定义starter需要的jar包,和rabbimq的jar
2.spring.factories:声明自己的starter的自动配置类
4.自动配置:其他项目启动时,自动配置这个配置类下的Bean
5.rabbitmqConfig.properties:统一管理rabbitmq的配置
一、封装的需求
自己写个练手的项目,对于mq这块的需求,画个图表达一下:
所以最终目标是依赖了自己封装的Starter项目在配置文件中定义要给哪个主题号发,要接收哪个主题号的消息就好,而且对于mq的配置,希望所有集群大体上遵循一个原则设置,配置文件要同意管理,详细看下面代码吧!自定义starter的整体构造如下:
二、Starter代码
1.pom:依赖自定义starter需要的jar包,和rabbimq的jar
<dependencies>
<!-- 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.8</version>
</dependency>
<!-- 编译时依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.6.8</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.6.8</version>
</dependency>
</dependencies>
2.spring.factories:声明自己的starter的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.mall.seckill.autoconfigure.XxxMqAutoConfiguration
3.自定义starter的配置格式
/**
* 希望所有用这个starter的项目,用下面这个方式来定义主题和动作
* xxx:
* mq:
* topics:
* starter.test1.topic: starter.test1.topic
* starter.test2.topic: starter.test2.topic
* starter.test3.topic: starter.test3.topic
* starter.test4.topic: starter.test4.topic
* actions:
* starter.test1.topic: send
* starter.test2.topic: receive
* starter.test3.topic: receive
* starter.test4.topic: receive
* 简单解释一下:xxx.mq.topic下自定义了4个配置,配置名是随便起的,方便使用者用@Value的方式注入自己想要发送或接收的主题
*/
@ConfigurationProperties(prefix = "xxx.mq")
public class XxxMqProperties {
private Map<String, String> topics = new HashMap<>();
private Map<String, XxxMqAction> actions = new HashMap<>();
}
4.自动配置:其他项目启动时,自动配置这个配置类下的Bean
@Slf4j
@Configuration
@EnableConfigurationProperties(XxxMqProperties.class)
@PropertySource("classpath:/rabbitmqConfig.properties") // 让其他项目可以加载这个配置,因为springboot项目默认加载配置文件是找不到这个starter下rabbitmqConfig.properties的
//@ConditionalOnClass(RabbitTemplate.class)
//@ConditionalOnProperty(prefix = "xxx.mq", name = "enabled", havingValue = "true")
public class XxxMqAutoConfiguration {
/**
* 配置文件上的内容
* 以xxx.mq开头的配置内容
*/
private final XxxMqProperties properties;
/**
* 定义一个异常消息收集的地方
* 正常要落库或者日志,个人做的小项目练手,就不写那么多了
*/
private final String errorMessageTopic = "error.message.topic";
private final String errorMessageTopicQueue = "error.message.topic.queue";
/**
* 引入spring-boot-starter-amqp就自动配置了
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 引入spring-boot-starter-amqp就自动配置了
* 这是创建监听者的工厂,他会以配置文件的配置信息,创建一个SimpleRabbitListenerContainer,用来监听消息用的
*/
@Autowired
private SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory;
/**
* spring容器
*/
@Autowired
private ApplicationContext applicationContext;
public XxxMqAutoConfiguration(XxxMqProperties properties) {
this.properties = properties;
}
//------------------mq全局的配置相关------------------
/**
* 定义一些工具方法
* @return
*/
@Bean
public XxxMqUtils xxxMqUtils() {
return new XxxMqUtils();
}
/**
* mq消息自动转实体类
* @return
*/
@Bean
public MessageConverter messageConverter() {
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
jackson2JsonMessageConverter.setCreateMessageIds(true);
return jackson2JsonMessageConverter;
}
/**
* 消费者消费不了的异常消息,使用RepublishMessageRecoverer这个机制,可以将异常消息发送到这个交换机上,不至于一直重试
* @param rabbitTemplate
* @return
*/
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
return new RepublishMessageRecoverer(rabbitTemplate, "error.message.topic");
}
/**
* 引入这个starter,自动配置这个自定义的发送消息的方法
* @return
*/
@Bean
public XxxMqSender xxxMqSender() {
return new XxxMqSender(rabbitTemplate);
}
/**
* 发送者确认机制,耗性能,不建议用
* @param rabbitTemplate
* @return
*/
// @PostConstruct
// public void init() {
// rabbitTemplate.setReturnsCallback(returned -> log.error("触发returnMessage=[{}]", JSON.toJSONString(returned)));
// }
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
/**
* 批量注册交换机
* @param rabbitAdmin
* @return
*/
@Bean
public List<FanoutExchange> exchanges(RabbitAdmin rabbitAdmin) {
List<FanoutExchange> exchanges = new ArrayList<>();
Map<String, String> topics = properties.getTopics();
for (String topic : topics.values()) {
FanoutExchange exchange = new FanoutExchange(topic);
exchange.setShouldDeclare(true);
exchange.setDelayed(true);
exchanges.add(exchange);
rabbitAdmin.declareExchange(exchange);
log.info("成功注册交换机=[{}]", topic);
}
FanoutExchange exchange = new FanoutExchange(errorMessageTopic);
exchange.setShouldDeclare(true);
exchange.setDelayed(true);
exchanges.add(exchange);
rabbitAdmin.declareExchange(exchange);
log.info("成功注册交换机=[{}]", errorMessageTopic);
return exchanges;
}
/**
* 批量注册队列
* @param rabbitAdmin
* @param xxxMqUtils
* @return
*/
@Bean
public List<Queue> queues(RabbitAdmin rabbitAdmin, XxxMqUtils xxxMqUtils) {
List<Queue> queues = new ArrayList<>();
Map<String, String> topics = properties.getTopics();
Map<String, XxxMqAction> actions = properties.getActions();
for (String topic : topics.values()) {
XxxMqAction action = actions.get(topic);
if (action.equals(XxxMqAction.RECEIVE) || action.equals(XxxMqAction.SEND_AND_RECEIVE)) {
String queueName = xxxMqUtils.getQueueName(topic);
Queue queue = new Queue(queueName);
queue.setShouldDeclare(true);
queues.add(queue);
rabbitAdmin.declareQueue(queue);
log.info("成功注册queue=[{}]", queueName);
}
}
Queue queue = new Queue(errorMessageTopicQueue);
queue.setShouldDeclare(true);
queues.add(queue);
rabbitAdmin.declareQueue(queue);
log.info("成功注册queue=[{}]", errorMessageTopicQueue);
return queues;
}
/**
* 批量将队列绑定到交换机上
* 并且为队列创建一个监听类
* @param rabbitAdmin
* @param exchanges
* @param queues
* @param xxxMqUtils
* @param messageConverter
* @return
*/
@Bean
public List<Binding> bindings(RabbitAdmin rabbitAdmin,
List<FanoutExchange> exchanges,
List<Queue> queues,
XxxMqUtils xxxMqUtils,
MessageConverter messageConverter) {
List<Binding> bindings = new ArrayList<>();
for (FanoutExchange exchange : exchanges) {
log.info("exchange=[{}]", exchange.getName());
for (Queue queue : queues) {
log.info("queue=[{}]", queue.getName());
if (queue.getName().contains(exchange.getName())) {
Binding binding = BindingBuilder.bind(queue).to(exchange);
bindings.add(binding);
rabbitAdmin.declareBinding(binding);
log.info("队列=[{}]绑定到交换机=[{}]", queue.getName(), exchange.getName());
}
}
}
// 用simpleRabbitListenerContainerFactory来创建监听容器,可以使用配置文件的配置,如消费者的重试策略等等
Map<String, XxxMqReceiver> receivers = applicationContext.getBeansOfType(XxxMqReceiver.class);
for (XxxMqReceiver receiver : receivers.values()) {
SimpleMessageListenerContainer container = simpleRabbitListenerContainerFactory.createListenerContainer();
container.addQueueNames(xxxMqUtils.getQueueName(receiver.getTopic()));
// 给监听容器配置监听类,默认会使用监听类的handlerMessage方法,所以这里没有定义消费方法
container.setMessageListener(new MessageListenerAdapter(receiver, messageConverter));
log.info("container=[{}]", JSON.toJSONString(container));
// 给这个监听容器启动
container.start();
}
return bindings;
}
}
5.rabbitmqConfig.properties:统一管理rabbitmq的配置
spring.main.allow-circular-references=true
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=1s
# 开启连接失败重试
spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.initial-interval=700ms
spring.rabbitmq.template.retry.multiplier=1
spring.rabbitmq.template.retry.max-attempts=3
# none:关闭;simple:同步阻塞;correlated:异步回调
spring.rabbitmq.publisher-confirm-type=correlated
# 开启发送者ack消息
spring.rabbitmq.publisher-returns=true
# 消费者支持接收消息的最大量,每次只能获取两条消息
spring.rabbitmq.listener.simple.prefetch=2
# 消费者回复消息方案,自动ack,异常nack,转换异常reject
spring.rabbitmq.listener.simple.acknowledge-mode=auto
# 开启失败重试
spring.rabbitmq.listener.simple.retry.enabled=true
# 失败等待时长
spring.rabbitmq.listener.simple.retry.initial-interval=3000ms
# 下次失败的等待时长倍数
spring.rabbitmq.listener.simple.retry.multiplier=1
# 最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=3
# true无状态;false有状态;如果业务中包含事务,这里改为false
spring.rabbitmq.listener.simple.retry.stateless=true
6.封装
/**
* 当用到xxx.mq.action时,必须用枚举
* @Author: zhao zj
* @date: 2024/10/14
* @description:
*/
public enum XxxMqAction {
SEND,
RECEIVE,
SEND_AND_RECEIVE,
;
}
----------------------------------------
/**
* 其他项目实现这个类用于接收消息
* 泛型时消息对象的类型
* @Author: zhao zj
* @date: 2024/10/13
* @description:
*/
public interface XxxMqReceiver<T> {
void handleMessage(T t);
/**
* 这里其实就是交换机的名称
* @return
*/
String getTopic();
}
----------------------------------------
/**
* 简单封装一下,不复杂
* @Author: zhao zj
* @date: 2024/10/14
* @description:
*/
public class XxxMqSender {
private RabbitTemplate rabbitTemplate;
public XxxMqSender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void send(Object object, Integer delay, String topic) {
rabbitTemplate.convertAndSend(topic, null, object, message -> {
MessageProperties messageProperties = message.getMessageProperties();
if (Objects.nonNull(delay)) {
messageProperties.setDelay(delay);
}
messageProperties.setMessageId(UUID.randomUUID().toString());
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
});
}
public void send(Object object, String topic) {
send(object, null, topic);
}
}
----------------------------------------
public class XxxMqUtils {
@Value("${spring.application.name}")
private String clientName;
/**
* 生成队列名称的规则,这样可以统一管理队列
* @param topic
* @return
*/
public String getQueueName(String topic) {
return clientName + "." + topic + ".queue";
}
}
三、使用这个Starter
1.pom依赖:只引入自定义的starter
<dependency>
<groupId>com.xxx.mall.seckill</groupId>
<artifactId>xxx-mq-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.生产者
@Test
public void setStarterTest1Topic() {
UserInfoDto userInfoDto = new UserInfoDto();
userInfoDto.setName("zhangsan");
xxxMqSender.send(userInfoDto, starterTest1Topic);
}
3.消费者
@Slf4j
@Component
public class StarterTest2MqReceiver implements XxxMqReceiver<UserInfoDto> {
@Value("${xxx.mq.topics.starter.test1.topic}")
private String starterTest1Topic;
@Override
public void handleMessage(UserInfoDto message) {
log.info("handleMessage:{}", JSON.toJSONString(message));
}
@Override
public String getTopic() {
return starterTest1Topic;
}
}