封装spring-boot-starter-amqp为自己的Starter

抛砖引玉,如果有更好建议,请提给我~

目录

一、封装的需求

二、Starter代码

1.pom:依赖自定义starter需要的jar包,和rabbimq的jar

 2.spring.factories:声明自己的starter的自动配置类

3.自定义starter的配置格式

4.自动配置:其他项目启动时,自动配置这个配置类下的Bean

5.rabbitmqConfig.properties:统一管理rabbitmq的配置

6.封装

三、使用这个Starter

1.pom依赖:只引入自定义的starter

2.生产者

3.消费者

3.测试


一、封装的需求

自己写个练手的项目,对于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;
    }

}

3.测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值