栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

自定义springboot组件--基于模板模式对原生springboot的rabbitmq组件进行扩展

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自定义springboot组件--基于模板模式对原生springboot的rabbitmq组件进行扩展

一 引入

在我们的日常开发中,消息中间件已经成为了java研发工程师的一项必备技能,本文主要是基于对springboot原生组件的扩展开发,基于模板设计模式和静态代理模式,简化了队列路由的绑定,交由公共模板进行统一的绑定,并在公用模板中保证了消息的幂等性和消息的可靠性投递,将这些类似的代码抽离出来,让开发者只专注于业务逻辑的开发.

整体实现思路:

  • 开发者申明路由交换机等基础元数据后,交由元数据解析器完成交换机,路由的申明及相应关系的绑定
  • 开发者申明消息监听器后,通过消息侦听容器完成队列与监听器的绑定
  • 开发者通过消息发射器,发布消息后,系统通过不通模板完成消息的发送
二 逻辑实现 2.1 元数据解析器的构建

申明基础信息接口

public interface MessageMetaData {
    
    String getQueue();

    
    ExchangeTypeEnum getExchangeType();

    
    default Map getQueueArgs(){
        return null;
    };

    
    default Map getExchangeArgs(){
        return null;
    };

    
    default MessageProperties getMessageProperties(){
        return null;
    };

    default void setMessageProperties(MessageProperties messageProperties){

    };

}

定义不同的元数据模板

public abstract class FanoutMessageMetaData implements MessageMetaData {

    private MessageProperties messageProperties = null;

    @Override
    public ExchangeTypeEnum getExchangeType() {
        return ExchangeTypeEnum.FANOUT;
    }

    
    abstract public String getExchange();


    @Override
    public MessageProperties getMessageProperties() {
        return this.messageProperties;
    }

    @Override
    public void setMessageProperties(MessageProperties messageProperties) {
        this.messageProperties=messageProperties;
    }
}

public abstract class DirectMessageMetadata implements MessageMetaData{
    private MessageProperties messageProperties;
    @Override
    public ExchangeTypeEnum getExchangeType() {
        return ExchangeTypeEnum.DIRECT;
    }

    
    abstract public String getExchange();

    @Override
    public MessageProperties getMessageProperties() {
        return this.messageProperties;
    }

    @Override
    public void setMessageProperties(MessageProperties messageProperties) {
        this.messageProperties=messageProperties;
    }
}

申明元数据解析接口,定义解析规范

public interface Support {
    Boolean support(ExchangeTypeEnum exchangeTypeEnum);
}


public interface MessageMetaDataResolver extends Support {
    
    void resolve(MessageMetaData messageMetaData);
}

定义不同模板的解析器

@RequiredArgsConstructor
@Slf4j
public abstract class AbstractMessageMetadataResolver implements MessageMetaDataResolver {
    private final RabbitAdmin rabbitAdmin;


    @Override
    public void resolve(MessageMetaData messageMetaData) {
        if (support(messageMetaData.getExchangeType())){
            doResolve(messageMetaData);
        }
    }

    
    abstract void doResolve(MessageMetaData messageMetaData);

    
    public void declareQueue(Queue queue){
        rabbitAdmin.declareQueue(queue);
        log.info("queue [{}] declared.",queue);
    }

    public void declareExchange(Exchange exchange){
        rabbitAdmin.declareExchange(exchange);
        log.info("exchange [{}] declared.",exchange);
    }

    public void declareBinding(Binding binding){
        rabbitAdmin.declareBinding(binding);
        log.info("binding [{}] declared.",binding);
    }

}
public class DirectMessageMetadataResolver extends AbstractMessageMetadataResolver{

    public DirectMessageMetadataResolver(RabbitAdmin rabbitAdmin) {
        super(rabbitAdmin);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return ExchangeTypeEnum.DIRECT.equals(exchangeTypeEnum);
    }

    @Override
    void doResolve(MessageMetaData messageMetaData) {
        DirectMessageMetadata directMessageMetadata = (DirectMessageMetadata) messageMetaData;

        // 申明队列
        Queue queue = new Queue(directMessageMetadata.getQueue(), true, false, false, directMessageMetadata.getQueueArgs());
        declareQueue(queue);

        // 申明交换机
        DirectExchange exchange = new DirectExchange(directMessageMetadata.getExchange(), true, false, directMessageMetadata.getExchangeArgs());
        declareExchange(exchange);

        // 交换机绑定队列
        Binding binding = BindingBuilder.bind(queue).to(exchange).withQueueName();
        declareBinding(binding);
    }
}

public class FanoutMessageMetaDateResolver extends AbstractMessageMetadataResolver{
    public FanoutMessageMetaDateResolver(RabbitAdmin rabbitAdmin) {
        super(rabbitAdmin);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return ExchangeTypeEnum.FANOUT.equals(exchangeTypeEnum);
    }

    @Override
    void doResolve(MessageMetaData messageMetaData) {
        FanoutMessageMetaData FanoutMessageMetaData = (FanoutMessageMetaData) messageMetaData;
        // 申明队列
        Queue queue = new Queue(FanoutMessageMetaData.getQueue(), true, false, false, FanoutMessageMetaData.getQueueArgs());
        declareQueue(queue);

        // 申明交换机
        FanoutExchange exchange = new FanoutExchange(FanoutMessageMetaData.getExchange(), true, false, FanoutMessageMetaData.getExchangeArgs());
        declareExchange(exchange);

        // 队列绑定交换机
        Binding binding = BindingBuilder.bind(queue).to(exchange);
        declareBinding(binding);

    }
}

申明静态代理器

@Slf4j
public class DelegatingMessageMetaDataResolver implements MessageMetaDataResolver {

    private final Map messageMetaDataResolverMap = new ConcurrentHashMap<>();

    public DelegatingMessageMetaDataResolver(Map messageMetaDataResolverMap){
       this.messageMetaDataResolverMap.putAll(messageMetaDataResolverMap);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return true;
    }

    @Override
    public void resolve(MessageMetaData messageMetaData) {
        if (messageMetaData==null){
            log.error("metaDate resolve must have messageMetaData but is null");
            return;
        }
        MessageMetaDataResolver messageMetaDataResolver = messageMetaDataResolverMap.get(messageMetaData.getExchangeType());
        if (messageMetaDataResolver==null){
            log.error("messageMetaDataResolver is null");
            return;
        }
        messageMetaDataResolver.resolve(messageMetaData);
    }
}

元数据初始化器

@RequiredArgsConstructor
@Slf4j
public class MessageMetaDataInitalizer implements InitializingBean, Ordered {

    private final XlcpMqProperties xlcpMqProperties;

    private final MessageMetaDataResolver messageMetaDataResolver;

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("autoCreateMq configured with [{}]",xlcpMqProperties.getAutoCreateMq());
        if (xlcpMqProperties.getAutoCreateMq()){
            Map messageMetaDataMap = SpringContextHolder.getBeansOfType(MessageMetaData.class);
            messageMetaDataMap.forEach((key,messageMetaData)->{
                log.info("Mq auto declared with metaDate [{}]",messageMetaData);
                messageMetaDataResolver.resolve(messageMetaData);
            });
        }
    }


    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

将静态代理器和初始器交由spring容器管理

2.2 动态消息监听器构建

申明抽象消息监听器

@Slf4j
public abstract class DynamicMessageListener extends AbstractAdaptableMessageListener {
    private final MessageMetaData messageMetaData;

    public static final String MQ_MESSAGE_ID_PREFIX = "mq:message:id";

    public DynamicMessageListener(MessageMetaData messageMetaData){
        this.messageMetaData=messageMetaData;
    }

    public void doExcute(Message message, Channel channel, Consumer consumer) throws IOException {

        MessageProperties messageProperties=message.getMessageProperties();
        String messageId=messageProperties.getMessageId();

        // 校验当前消息是否被消费
        RedisTemplate redisTemplate=SpringContextHolder.getBean(RedisTemplate.class);

        redisTemplate.setKeySerializer(new StringRedisSerializer());

        String mqMessageMqKey = String.format("%s%s%s",MQ_MESSAGE_ID_PREFIX, StrPool.COLON,messageId);

        if (redisTemplate.hasKey(mqMessageMqKey)){
            ackOk(message,channel);
            log.info("当前消息已经被消费了,消息id为:{}",messageId);
            return;
        }
        MessageConverter messageConverter=getMessageConverter();
        Object convetMessag= messageConverter.fromMessage(message);
        Boolean ackFalg = Boolean.FALSE;
        try {
            consumer.accept(convetMessag);
            // 存入redis中确保消息已经被消费了 保存30分钟
            redisTemplate.opsForValue().set(mqMessageMqKey,"",30, TimeUnit.MINUTES);
        } catch (Exception exception) {
            exception.printStackTrace();
            // 签收失败将消息重新投递到队列中
            ackFalg=onError(message,channel);
        }finally {
            if (!ackFalg) {
                ackOk(message, channel);
            }
        }

    }

    protected Boolean onError(Message message,Channel channel) throws IOException {
        channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        return Boolean.TRUE;
    }

    protected void ackOk(Message message,Channel channel) throws IOException {
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    public MessageMetaData getMessageMetaData(){
        return messageMetaData;
    }
}
 

申明单消息监听器模板和批量消息监听模板

public abstract class SingleDynamicMessageListener extends DynamicMessageListener{
    public SingleDynamicMessageListener(MessageMetaData messageMetaData) {
        super(messageMetaData);
    }

    public abstract void onMessage(T t);

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        doExcute(message,channel,object->{
            onMessage((T)object);
        });
    }


}

public abstract class BatchDynamicMessageListener extends DynamicMessageListener {

    public BatchDynamicMessageListener(MessageMetaData messageMetaData) {
        super(messageMetaData);
    }

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        MessageConverter messageConverter=getMessageConverter();
        doExcute(message,channel,(obj)->{
            if (message instanceof Collection){
                onMessageBatch((Collection) obj);
            }else {
                ArrayList list = new ArrayList<>();
                list.add((T)message);
                onMessageBatch(list);
            }
        });
    }

    
    abstract void onMessageBatch(Collection collection);
}

申明动态容器监听器

@RequiredArgsConstructor
public class DynamicMessageListenerContainer extends SimpleRabbitListenerContainerFactory {
    private final int DEFAULT_PREFETCH_COUNT = 1;

    private final ConnectionFactory connectionFactory;

    
    public static final String LISTENER_CONTAINER_SUFFIX = "SimpleMessageListenerContainer";

    private int prefetchCount = DEFAULT_PREFETCH_COUNT;

    public SimpleMessageListenerContainer createListenerContainer(DynamicMessageListener dynamicMessageListener, MessageMetaData messageMetaData){
        SimpleMessageListenerContainer simpleMessageListenerContainer=new SimpleMessageListenerContainer();
        simpleMessageListenerContainer.setQueueNames(messageMetaData.getQueue());
        simpleMessageListenerContainer.setMessageListener(dynamicMessageListener);
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
        simpleMessageListenerContainer.setPrefetchCount(prefetchCount);
        //设置当前消息为手动签收
        simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return simpleMessageListenerContainer;
    }

    public void setPrefetchCount(int prefetchCount) {
        this.prefetchCount = prefetchCount;
    }
}

申明动态容器监听器初始化容器

@Slf4j
public class DynamicMessageListenerInitialize implements InitializingBean, Ordered {

    @Override
    public void afterPropertiesSet() throws Exception {
        // 获得所有客户端配置的监听器
        Map listenerMap = SpringContextHolder.getBeansOfType(DynamicMessageListener.class);

        // 获取bean工厂
        ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory)SpringContextHolder.getBeanFactory();

        // 获得监听器容器 同于配置配置监听器
        DynamicMessageListenerContainer dynamicMessageListenerContainer = SpringContextHolder.getBean(DynamicMessageListenerContainer.class);

        // 初始化监听器的配置
        listenerMap.forEach((listenerName,listener)->{
            MessageMetaData messageMetaData = listener.getMessageMetaData();
            SimpleMessageListenerContainer listenerContainer = dynamicMessageListenerContainer.createListenerContainer(listener, messageMetaData);
            String beanName = String.format("%s%s",listenerName,DynamicMessageListenerContainer.LISTENER_CONTAINER_SUFFIX);
            beanFactory.registerSingleton(beanName,listenerContainer);
            log.info("create [{}] listener to [{}]",beanName,messageMetaData);
        });
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

}

将初始化容器交给spring容器管理

2.3 消息发射器构建

申明发射规范

public interface MessageDelivery extends Support {
    
     Boolean deliver(MessageMetaData messageMetaData,T t);
}

申明不同模板的发射器

public abstract class AbstractMessageDelivery implements MessageDelivery {
    public final RabbitTemplate rabbitTemplate;

    public AbstractMessageDelivery(RabbitTemplate rabbitTemplate){
        this.rabbitTemplate=rabbitTemplate;
    }

    @Override
    public  Boolean deliver(MessageMetaData messageMetaData, T t) {
        if (support(messageMetaData.getExchangeType())){
            doDeliver(messageMetaData,t);
            return true;
        }
        return false;
    }

    public abstract void doDeliver(MessageMetaData messageMetaData,T t);

    public Message createMessage(Object object,MessageMetaData messageMetaData){
        // 消息转换 并设置当前消息全局唯一id保证消息不被重复下消费
        SimpleMessageConverter converter = new SimpleMessageConverter();
        final MessageProperties messageProperties;
        MessageProperties customMessageProperties = messageMetaData.getMessageProperties();
        if (customMessageProperties!=null){
            messageProperties=customMessageProperties;
        }else {
            messageProperties = MessagePropertiesBuilder.newInstance()
                    .build();
        }
        Message message = converter.toMessage(object, setSharedMessageProperties(messageProperties));
        return message;
    }

    
    private MessageProperties setSharedMessageProperties(MessageProperties messageProperties){
        messageProperties.setMessageId(Convert.toStr(UniqueIdUtil.genId()));
        return messageProperties;
    }
}

public class FanoutMessageMetaDateDeliver extends AbstractMessageDelivery {

    public FanoutMessageMetaDateDeliver(RabbitTemplate rabbitTemplate) {
        super(rabbitTemplate);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return ExchangeTypeEnum.FANOUT.equals(exchangeTypeEnum);
    }

    @Override
    public  void doDeliver(MessageMetaData messageMetaData, T t) {
        FanoutMessageMetaData FanoutMessageMetaData = (FanoutMessageMetaData) messageMetaData;
        Message message = createMessage(t, messageMetaData);
        rabbitTemplate.convertAndSend(FanoutMessageMetaData.getExchange(),"",message);
    }
}

public class DirectMessageMetaDateDeliver extends AbstractMessageDelivery{

    public DirectMessageMetaDateDeliver(RabbitTemplate rabbitTemplate) {
        super(rabbitTemplate);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return ExchangeTypeEnum.DIRECT.equals(exchangeTypeEnum);
    }

    @Override
    public  void doDeliver(MessageMetaData messageMetaData, T t) {
        DirectMessageMetadata directMessageMetadata = (DirectMessageMetadata) messageMetaData;
        Message message = createMessage(t,messageMetaData);
        rabbitTemplate.convertAndSend(directMessageMetadata.getExchange(),directMessageMetadata.getQueue(),message);
    }
}

申明静态代理器


public class DelegatingMessageDelivery implements MessageDelivery {

    private final Map messageDeliveryMap = new ConcurrentHashMap<>();

    public DelegatingMessageDelivery(Map messageDeliveryMap){
        this.messageDeliveryMap.putAll(messageDeliveryMap);
    }

    @Override
    public Boolean support(ExchangeTypeEnum exchangeTypeEnum) {
        return true;
    }

    @Override
    public  Boolean deliver(MessageMetaData messageMetaData, T t) {
        Assert.notNull(messageMetaData,"message deliver must have messageMetaData but is null");
        ExchangeTypeEnum exchangeType = messageMetaData.getExchangeType();
        MessageDelivery delivery = messageDeliveryMap.get(messageMetaData.getExchangeType());
        Assert.notNull(delivery,"message deliver not null");
        return delivery.deliver(messageMetaData,t);
    }
}
2.4 申明公用基础属性
@ConfigurationProperties(prefix = XlcpMqProperties.PRE_FIX)
@Data
public class XlcpMqProperties {
    public final static String PRE_FIX = "config.mq";

    private Boolean enabled = Boolean.TRUE;

    
    private Boolean autoCreateMq = Boolean.TRUE;
}
2.5定义springboot基础规范
@Configuration
@EnableConfigurationProperties(XlcpMqProperties.class)
public class XlcpMqMessageAutoConfiguration {
    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        return new RabbitAdmin(connectionFactory);
    }

    
    @Bean
    @ConditionalOnMissingBean
    public MessageMetaDataResolver messageMetaDataResolver(RabbitAdmin rabbitAdmin){
        HashMap resolverHashMap = new HashMap<>();
        resolverHashMap.put(ExchangeTypeEnum.FANOUT,new FanoutMessageMetaDateResolver(rabbitAdmin));
        resolverHashMap.put(ExchangeTypeEnum.DIRECT,new DirectMessageMetadataResolver(rabbitAdmin));
        return new DelegatingMessageMetaDataResolver(resolverHashMap);
    }

    
    @Bean
    @ConditionalOnMissingBean
    public MessageMetaDataInitalizer messageMetaDataInitalizer(MessageMetaDataResolver messageMetaDataResolver,XlcpMqProperties xlcpMqProperties){
        return new MessageMetaDataInitalizer(xlcpMqProperties,messageMetaDataResolver);
    }

    
    @Bean
    @ConditionalOnMissingBean
    public MessageDelivery messageDelivery(RabbitTemplate rabbitTemplate){
        HashMap deliveryHashMap = new HashMap<>();
        deliveryHashMap.put(ExchangeTypeEnum.FANOUT,new FanoutMessageMetaDateDeliver(rabbitTemplate));
        deliveryHashMap.put(ExchangeTypeEnum.DIRECT,new DirectMessageMetaDateDeliver(rabbitTemplate));
        return new DelegatingMessageDelivery(deliveryHashMap);
    }

    @Bean
    public DynamicMessageListenerContainer dynamicMessageListenerContainer(ConnectionFactory connectionFactory){
        return new DynamicMessageListenerContainer(connectionFactory);
    }

    @Bean
    public DynamicMessageListenerInitialize dynamicMessageListenerInitialize(){
        return new DynamicMessageListenerInitialize();
    }
}

三 客户端使用 3.1 申明自定义元数据信息
@Component
public class DemoMessageMetaData extends FanoutMessageMetaData {
    @Override
    public String getExchange() {
        return "demoExchange";
    }

    @Override
    public String getQueue() {
        return "demoQueue";
    }


}
3.2 申明消息监听器
@Data
public class DemoMessage implements Serializable {

    private static final long serialVersionUID = -5307128314859934844L;

    private String text;
}

@Component
@Slf4j
public class DemoMessageListener extends SingleDynamicMessageListener {

    public DemoMessageListener() {
        super(new DemoMessageMetaData());
    }

    @Override
    public void onMessage(DemoMessage demoMessage) {
        log.info("监听到消息:{}",demoMessage);
    }
}
3.3 业务代码中使用
@RestController
@RequestMapping("/mq")
@Slf4j
public class DemoMessageController {
    @Autowired
    private DemoMessageMetaData demoMessageMetaData;
    @Autowired
    private MessageDelivery messageDelivery;
    
    @GetMapping("/message")
    @Inner(value = false)
    public void sendSingleMessage(){
        DemoMessage demoMessage = new DemoMessage();
        demoMessage.setText("hello mq");
        MessageProperties messageProperties = MessagePropertiesBuilder.newInstance()
                .setExpiration("1")
                .setDeliveryTag(1234L)
                .build();
        demoMessageMetaData.setMessageProperties(messageProperties);
        messageDelivery.deliver(demoMessageMetaData,demoMessage);
    }
}
四 一些说明 4.1 如何保证消息的幂等性?

4.2 如何保证消息的可靠性投递


主要从三个方面入手:
开启数据的持久化,避免服务宕机后重启消息丢失

消息的投递:通过confirm回调确保消息投递成功


消费端主要是通过手动签收来确保消息被消费成功

4.3 如何基于mq实现消息的延迟消费




注意:这种方式当最前面的消息没过期时后面的消息过期了也不会投递到死信交换机中,这里容易导致消息的堆积,一般用于时长固定的定时任务,可以采用rocketMq进行可变的延时消息的投递.

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号