mq底层抽象--Spring动态多MQ配置组件抽象| 8月更文挑战

887 阅读5分钟

经过一周多的时间,终于完成了阿里云mq的抽象。 这次抽象原本是想将答题服务中【发送主观题答题数据给批改中间件】这块抽象出来, 但这块主要使用的是mq消息的方式发送到批改中间件,所以,最后决定将mq进行抽象,抽象后的结果是:抽象成通用可个性化的配置mq,且可以扩展到任何使用mq的业务场景上。终端需要做的就是增加mq配置,自定义消费者业务逻辑方法,调用send方法即可。

这样做的好处是:原本在每个使用到mq的项目里都要写一遍mq生产者,mq消费者,发送mq数据,监听mq消费等动作,且如果一个项目里有多个mq配置,要写多遍这样的配置。抽象后,只需要配置文件中进行配置,然后自定义个性化的业务逻辑消费者,就可以进行mq发送了。

下面来研究到底是如何实现的呢?

做这样一个抽象, 我们首先要分析清楚这个需求, 需求拆解清楚了, 才更容易找到解决方案.

1.mq有多种形式, 我们常用的有普通mq和顺序型mq. 后面可能还有其他类型mq, 需要支持动态扩展

2.一个服务可能有多种类型的mq, 每一种类型的mq可能有多套配置. 需要支持动态配置.

3.mq和数据源一样, 服务启动对应的生产者和消费者就产生了, 等待发送数据和消费数据

4.客户端能够通过关键字定位到指定的mq, 并向mq发送消息.

5.消费者逻辑需要自定义, 应为不同的业务需求也不同, 需要能通过反射发送到底层

以上几点正是这个组件的精髓, 解决了上面的几个问题, 也就解决了mq抽象的问题.

这样一个可动态配置的mq,要求还是挺多的,如何动态配置? 如何能够在服务器启动的时候就启动n个mq的生产者和消费者? 发送数据的时候, 怎么找到正确的mq发送呢?

其实, 我一直相信, 我遇到的问题, 肯定有大神已经遇到过, 并且已经有了成熟的解决方案了. 于是, 开始搜索行业内的解决方案, 找了很久也没找到,最后在王晓的提示下,发现Spring动态配置多数据源,和我这个需求类似。于是,开始花时间研究Spring动态多数据源的源码。下图就是Spring动态多数据源的整体流程图。

下面来深入研究Spring动态多数据源,因为这个高清楚了,就知道我们的mq实现原理了。下面分析的步骤适用于任何spring源码的分析

一、Spring配置文件的入口。

Spring动态多数据源,我们在使用的时候,直接引入jar,然后配置数据源就可以使用了。配置jar包如下

<dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
      <version>3.1.1</version>
</dependency>

那么为什么引入jar就能在项目里使用了呢?因为在jar包里配置了META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

这就是源码项目的入口了。这里定义了项目启动自动装备DynamicDataSourceAutoConfiguration文件。

二、DynamicDataSourceAutoConfiguration动态数据源配置文件

@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {

    private final DynamicDataSourceProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        return new YmlDynamicDataSourceProvider(datasourceMap);
    }

    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrict(properties.getStrict());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setProvider(dynamicDataSourceProvider);
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setSeata(properties.getSeata());
        return dataSource;
    }

    @Bean
    @ConditionalOnMissingBean
    public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
        DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor();
        interceptor.setDsProcessor(dsProcessor);
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
        advisor.setOrder(properties.getOrder());
        return advisor;
    }

    @Bean
    @ConditionalOnMissingBean
    public DsProcessor dsProcessor() {
        DsHeaderProcessor headerProcessor = new DsHeaderProcessor();
        DsSessionProcessor sessionProcessor = new DsSessionProcessor();
        DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
        headerProcessor.setNextProcessor(sessionProcessor);
        sessionProcessor.setNextProcessor(spelExpressionProcessor);
        return headerProcessor;
    }

    @Bean
    @ConditionalOnBean(DynamicDataSourceConfigure.class)
    public DynamicDataSourceAdvisor dynamicAdvisor(DynamicDataSourceConfigure dynamicDataSourceConfigure, DsProcessor dsProcessor) {
        DynamicDataSourceAdvisor advisor = new DynamicDataSourceAdvisor(dynamicDataSourceConfigure.getMatchers());
        advisor.setDsProcessor(dsProcessor);
        advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return advisor;
    }
}

看到这段代码,我们就比较熟悉了,这就是通过注解的方式,在项目启动的时候,自动注入bean。我们来详细看一下,他都注入了哪些内容。

  1. 动态多数据源预置处理器dsProcess,ds就是datasource的简称。这里主要采用的是责任链设计模式,获取ds。
  2. 动态多数据源注解通知dynamicDatasourceAnnotationAdvisor,这是一个aop前置通知,当一个请求发生的时候,会触发前置通知,用来确定到底使用哪一个mq消息队列
  3. 动态多数据源提供者dynamicDataSourceProvider,我们是动态配置多个数据源,那么就有一个解析配置的过程,解析配置就是在这里完成的,解析出多个数据源,然后分别调用数据源创建者去创建数据源。Spring动态多数据源支持数据源的嵌套。
  4. 动态路由到数据源DynamicRoutingDataSource,当请求过来的时候,也找到对应的数据源了,要建立数据库连接,数据库连接的操作就是在这里完成的。

我们发现在这里就有四个bean的初始化,并没有bean的create创建过程,bean的创建过程是在另一个配置类(DynamicDataSourceCreatorAutoConfiguration)中完成的。

@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceCreatorAutoConfiguration {

    private final DynamicDataSourceProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public DataSourceCreator dataSourceCreator() {
        DataSourceCreator dataSourceCreator = new DataSourceCreator();
        dataSourceCreator.setBasicDataSourceCreator(basicDataSourceCreator());
        dataSourceCreator.setJndiDataSourceCreator(jndiDataSourceCreator());
        dataSourceCreator.setDruidDataSourceCreator(druidDataSourceCreator());
        dataSourceCreator.setHikariDataSourceCreator(hikariDataSourceCreator());
        dataSourceCreator.setGlobalPublicKey(properties.getPublicKey());
        return dataSourceCreator;
    }

    @Bean
    @ConditionalOnMissingBean
    public BasicDataSourceCreator basicDataSourceCreator() {
        return new BasicDataSourceCreator();
    }

    @Bean
    @ConditionalOnMissingBean
    public JndiDataSourceCreator jndiDataSourceCreator() {
        return new JndiDataSourceCreator();
    }

    @Bean
    @ConditionalOnMissingBean
    public DruidDataSourceCreator druidDataSourceCreator() {
        return new DruidDataSourceCreator(properties.getDruid());
    }

    @Bean
    @ConditionalOnMissingBean
    public HikariDataSourceCreator hikariDataSourceCreator() {
        return new HikariDataSourceCreator(properties.getHikari());
    }
}

大概是因为考虑到数据的种类比较多,所以将其单独放到了一个配置里面。从上面的源码可以看出,有四种类型的数据源配置。分别是:basic、jndi、druid、hikari。这四种数据源通过组合设计模式被set到DataSourceCreator中。

以上就是源码的第一部分,也是整个项目的框架部分。对应的上面的项目框架图是:

接下来,分别来看每一个模块都做了哪些事情。