美文网首页Java
一文搞懂MyBatis多数据源Starter实现

一文搞懂MyBatis多数据源Starter实现

作者: 喝杯Java润润BUG | 来源:发表于2023-04-16 13:22 被阅读0次
    MyBatis

    前言

    本文将实现一个MyBatisSpringbootStarter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成MyBatis多数据源的初始化和使用,相较于MyBatis官方的Starter包,扩展了多数据源的使用场景。

    本文的所有源码可以从如下仓库下载。

    multidatasource GitHub

    Springboot版本:2.7.6

    正文

    一. 实现思路

    要实现Starter包,肯定需要借助Springboot的自动装配机制,所以我们首先需要提供自动装配的配置类。

    然后我们需要加载多个数据源的配置并且生成对应的数据源,同时还需要可以根据用户配置的type创建不同的数据源,例如可以支持创建HikariCP,DruidTomcatJdbc的数据源。

    创建出来的数据源需要根据用户的配置,设置给不同的SqlSessionFactory,然后不同的SqlSessionFactory设置给不同的MapperScannerConfigurer,最终实现的效果就是一部分映射接口使用一个数据源,另一部分映射接口使用另一个数据源。

    最后,还需要提供一种手段,抑制Springboot原生的数据源加载,这个功能我们可以通过
    ApplicationContextInitializer这个扩展点来完成。

    整体的一个思维导图如下所示。

    二. 自动装配实现

    由于适配的是Springboot2.7.x版本,所以需要在resources\META-INF\spring目录下创建

    org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,且内容如下所示。

    com.lee.multidatasource.autoconfig.LeeMultiPersistenceAutoConfiguration
    

    上述的
    LeeMultiPersistenceAutoConfiguration就是完成自动装配的配置类,实现如下。

    @AutoConfiguration
    @Import({LeeMultiPersistenceConfiguration.class, DataSourceBeanPostProcessor.class,})
    public class LeeMultiPersistenceAutoConfiguration {}
    

    通过
    LeeMultiPersistenceAutoConfiguration导入了两个bean,一个是
    LeeMultiPersistenceConfiguration,用于加载配置以及创建数据源和MyBatisbean,另一个是
    DataSourceBeanPostProcessor,用于对
    LeeMultiPersistenceConfiguration创建的bean做一些后置处理。

    上述就是自动装配的实现,主要就是将
    LeeMultiPersistenceAutoConfiguration注册到容器中,而
    LeeMultiPersistenceAutoConfiguration也是整个Starter包实现的关键。

    三. 配置加载

    约定数据源和MyBatis的配置需要遵循如下规则。

    lee:
      persistence:
        dataSourceName1:
          datasource:
            type: ...
            max-lifetime: ...
            keep-alive-time: ...
            driver-class-name: ...
            url: ...
            username: ...
            password: ...
            pool-name: ...
          mybatis:
            configLocation: ...
            basePackage: ...
        dataSourceName2:
          datasource:
            max-lifetime: ...
            keep-alive-time: ...
            driver-class-name: ...
            url: ...
            username: ...
            password: ...
            pool-name: ...
          mybatis:
            configLocation: ...
            basePackage: ...
    

    lee.persistence的下一级的配置,是数据源的名字,可以由用户自定义,这个名字最终会作为数据源的beanIOC容器中的名字。


    lee.persistence.dataSourceName的下一级的配置,固定是两个配置项,其一是
    lee.persistence.dataSourceName.datasource,用于设置数据源相关的配置,其二是
    lee.persistence.dataSourceName.mybatis,用于设置MyBatis相关的配置。

    由于数据源的名字和个数都可以由用户自定义,那么很难基于@ConfigurationProperties注解来一步到位的完成上述数据源配置的加载,我们需要基于Environment来自行处理。

    下面来看一下用于处理数据源配置的
    LeeMultiPersistenceConfiguration的类图,如下所示。

    LeeMultiPersistenceConfiguration首先实现了EnvironmentAware接口,从而可以拿到Environment对象,其次实现了
    ImportBeanDefinitionRegistrar接口,从而可以向Spring注册BeanDefinition,那么实际上
    LeeMultiPersistenceConfiguration做的事情就是加载配置以及向Spring注册数据源和MyBatis相关组件的BeanDefinition。下面看一下
    LeeMultiPersistenceConfiguration实现的**registerBeanDefinitions() **方法。

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 加载并解析数据源配置
        MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
        List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
        // 为每个数据源注册BeanDefinition
        for (String persistenceName : persistenceNames) {
            registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
            registerSqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
            registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        }
    }
    

    本节就先分析一下数据源配置的加载和解析,具体就是
    LeeMultiPersistenceConfiguration
    parseMultiPersistenceProperties() 方法,如下所示。

    private MultiPersistenceProperties parseMultiPersistenceProperties() {
        MultiPersistenceProperties multiPersistenceProperties = new MultiPersistenceProperties();
        // 将数据源相关的配置加载为MultiPersistencePropertiesWrapper
        MultiPersistencePropertiesWrapper multiPersistencePropertiesWrapper = parseMultiPersistencePropertiesWrapper();
        List<String> persistenceNames = multiPersistencePropertiesWrapper.getPersistenceNames();
        // 遍历每一个数据源并拿到这个数据源下数据源相关配置和MyBatis相关配置
        for (String persistenceName : persistenceNames) {
            DataSourceProperties dataSourceProperties = multiPersistencePropertiesWrapper
                    .getPersistenceDataSourceProperties(persistenceName);
            MybatisExtendProperties mybatisProperties = multiPersistencePropertiesWrapper
                    .getPersistenceMybatisProperties(persistenceName);
            // 添加当前数据源的配置信息到MultiPersistenceProperties中
            multiPersistenceProperties.addPersistenceProperties(
                    persistenceName, dataSourceProperties, mybatisProperties);
        }
        return multiPersistenceProperties;
    }
    
    private MultiPersistencePropertiesWrapper parseMultiPersistencePropertiesWrapper() {
        Map<String, Map<String, Map<String, String>>> persistenceProperties;
    
        Binder binder = Binder.get(environment);
        // PERSISTENCE_PREFIX为lee.persistence
        persistenceProperties = binder.bind(PERSISTENCE_PREFIX, Bindable.of(Map.class)).get();
        persistencePropertiesCache = persistenceProperties;
    
        return new MultiPersistencePropertiesWrapper(persistenceProperties);
    }
    

    上述方法解析数据源配置是在
    parseMultiPersistencePropertiesWrapper() 方法中,思路是先将我们的数据源配置基于Binder解析为Map的形式,因为lee.persistence这个配置前缀已经是确定的,所以在Binderbind() 方法中传入的配置名就是lee.persistence,那么最终得到的配置的Map实际内容如下所示。

    // 数据源名就是上面示例的dataSourceName1
    // 配置类型就是datasource或mybatis
    Map[数据源名, Map[配置类型, Map[配置key, 配置value]]]
    

    通常上述这种层级很多的Map,无论是可读性还是易用性都很差,所以我们可以使用一个包装类来包装一下这种Map,并进行适当充血,来方便对这种多层级Map的使用,这里的包装类就是
    MultiPersistencePropertiesWrapper,如下所示。

    public class MultiPersistencePropertiesWrapper {
    
        private Map<String, Map<String, Map<String, String>>> multiPersistenceProperties;
    
        public MultiPersistencePropertiesWrapper(Map<String, Map<String, Map<String, String>>> multiPersistenceProperties) {
            this.multiPersistenceProperties = multiPersistenceProperties;
        }
    
        // 只对multiPersistenceProperties提供set方法
        public void setMultiPersistenceProperties(Map<String, Map<String, Map<String, String>>> multiPersistenceProperties) {
            this.multiPersistenceProperties = multiPersistenceProperties;
        }
    
        // 获取数据源的个数
        public int getPersistenceSize() {
            return multiPersistenceProperties.size();
        }
    
        // 获取所有数据源的名字
        public List<String> getPersistenceNames() {
            return new ArrayList<>(multiPersistenceProperties.keySet());
        }
    
        // 获取某个数据源对应的数据源的配置类DataSourceProperties
        public DataSourceProperties getPersistenceDataSourceProperties(String persistenceName) {
            DataSourceProperties dataSourceProperties = new DataSourceProperties();
            Map<String, Map<String, String>> persistenceProperties = multiPersistenceProperties.get(persistenceName);
            Map<String, String> persistenceDatasourceProperties = persistenceProperties.get(KEY_DATASOURCE);
            if (ObjectUtils.isNotEmpty(persistenceDatasourceProperties) || !persistenceDatasourceProperties.isEmpty()) {
                Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
                dataSourceProperties = binder.bind(StringUtils.EMPTY, Bindable.of(DataSourceProperties.class)).get();
            }
            return dataSourceProperties;
        }
    
        // 获取某个数据源对应的MyBatis的配置类MybatisExtendProperties
        public MybatisExtendProperties getPersistenceMybatisProperties(String persistenceName) {
            MybatisExtendProperties mybatisProperties = new MybatisExtendProperties();
            Map<String, Map<String, String>> persistenceProperties = multiPersistenceProperties.get(persistenceName);
            Map<String, String> persistenceMybatisProperties = persistenceProperties.get(KEY_MYBATIS);
            if (ObjectUtils.isNotEmpty(persistenceMybatisProperties) && !persistenceMybatisProperties.isEmpty()) {
                Binder binder = new Binder(new MapConfigurationPropertySource(persistenceMybatisProperties));
                mybatisProperties = binder.bind(StringUtils.EMPTY, Bindable.of(MybatisExtendProperties.class)).get();
            }
            return mybatisProperties;
        }
    
    }
    

    MultiPersistencePropertiesWrapper中仅提供了对数据源配置Mapset方法,然后提供了多个有具体含义的get方法,例如拿到数据源的个数,名字集合以及某个数据源的DataSourceProperties或者MybatisExtendProperties,当然,如何将某个数据源对应的配置的Map解析为DataSourcePropertiesMybatisExtendProperties,还是依靠的Binder,这里就不再赘述,可以自己看一下上述代码的具体实现。

    这里再多提一下,DataSourceProperties这个是Springboot提供的数据源的配置类,而MybatisExtendProperties是我们自定义的一个继承于MybatisProperties(MyBatis官方启动包提供)的配置类,主要是扩展了一个叫做basePackage的属性,用于配置映射接口路径,如下所示。

    public class MybatisExtendProperties extends MybatisProperties {
    
        private String basePackage;
    
        public String getBasePackage() {
            return basePackage;
        }
    
        public void setBasePackage(String basePackage) {
            this.basePackage = basePackage;
        }
    
    }
    

    现在回到
    LeeMultiPersistenceConfiguration
    **parseMultiPersistenceProperties() **方法,再贴出其实现如下。

    private MultiPersistenceProperties parseMultiPersistenceProperties() {
        MultiPersistenceProperties multiPersistenceProperties = new MultiPersistenceProperties();
        // 将数据源相关的配置加载为MultiPersistencePropertiesWrapper
        MultiPersistencePropertiesWrapper multiPersistencePropertiesWrapper = parseMultiPersistencePropertiesWrapper();
        List<String> persistenceNames = multiPersistencePropertiesWrapper.getPersistenceNames();
        // 遍历每一个数据源并拿到这个数据源下数据源相关配置和MyBatis相关配置
        for (String persistenceName : persistenceNames) {
            DataSourceProperties dataSourceProperties = multiPersistencePropertiesWrapper
                    .getPersistenceDataSourceProperties(persistenceName);
            MybatisExtendProperties mybatisProperties = multiPersistencePropertiesWrapper
                    .getPersistenceMybatisProperties(persistenceName);
            // 添加当前数据源的配置信息到MultiPersistenceProperties中
            multiPersistenceProperties.addPersistenceProperties(
                    persistenceName, dataSourceProperties, mybatisProperties);
        }
        return multiPersistenceProperties;
    }
    

    在完成所有数据源配置加载并且生成包装类后,我们做的事情就是遍历每一个数据源的名字,然后通过数据源名字从包装类中拿到对应的DataSourcePropertiesMybatisExtendProperties,最后添加到
    MultiPersistenceProperties中,而
    MultiPersistenceProperties就是我们最终希望得到的多数据源的配置类,如下所示。

    public class MultiPersistenceProperties {
    
        private final Map<String, PersistenceProperties> persistencePropertiesMap = new HashMap<>(HASH_MAP_INITIAL_SIZE);
        
        // 将DataSourceProperties和MybatisExtendProperties封装为PersistenceProperties
        public void addPersistenceProperties(String persistenceName,
                                             DataSourceProperties dataSourceProperties,
                                             MybatisExtendProperties mybatisProperties) {
            PersistenceProperties persistenceProperties = new PersistenceProperties(dataSourceProperties, mybatisProperties);
            persistencePropertiesMap.put(persistenceName, persistenceProperties);
        }
    
        public List<String> getPersistenceNames() {
            return new ArrayList<>(persistencePropertiesMap.keySet());
        }
    
        public PersistenceProperties getPersistenceProperties(String persistenceName) {
            return persistencePropertiesMap.get(persistenceName);
        }
    
        public DataSourceProperties getDataSourceProperties(String persistenceName) {
            PersistenceProperties persistenceProperties = persistencePropertiesMap.get(persistenceName);
            if (ObjectUtils.isNotEmpty(persistenceProperties)) {
                return persistenceProperties.getDataSourceProperties();
            }
            throw new RuntimeException();
        }
    
        public MybatisExtendProperties getMybatisProperties(String persistenceName) {
            PersistenceProperties persistenceProperties = persistencePropertiesMap.get(persistenceName);
            if (ObjectUtils.isNotEmpty(persistenceProperties)) {
                return persistenceProperties.getMybatisProperties();
            }
            throw new RuntimeException();
        }
    
        public static class PersistenceProperties {
            private DataSourceProperties dataSourceProperties;
            private MybatisExtendProperties mybatisProperties;
    
            public PersistenceProperties(DataSourceProperties dataSourceProperties,
                                         MybatisExtendProperties mybatisProperties) {
                this.dataSourceProperties = dataSourceProperties;
                this.mybatisProperties = mybatisProperties;
            }
    
            public DataSourceProperties getDataSourceProperties() {
                return dataSourceProperties;
            }
    
            public void setDataSourceProperties(DataSourceProperties dataSourceProperties) {
                this.dataSourceProperties = dataSourceProperties;
            }
    
            public MybatisExtendProperties getMybatisProperties() {
                return mybatisProperties;
            }
    
            public void setMybatisProperties(MybatisExtendProperties mybatisProperties) {
                this.mybatisProperties = mybatisProperties;
            }
        }
    
    }
    

    我们在
    MultiPersistenceProperties中也进行了适当充血,首先将DataSourcePropertiesMybatisExtendProperties封装为了PersistenceProperties,然后将数据源名字作为key,数据源对应的PersistenceProperties作为value,存储到persistencePropertiesMap这个Map中,最后提供了若干get方法来实现对数据源对应的PersistenceProperties的访问。

    那么至此,我们就完成了本节一开始定义的多数据源配置的加载,最终加载完毕后得到的多数据源的配置类就是
    MultiPersistenceProperties,并且数据源个数,数据源名字和数据源类型完全可以自定义。

    四. 数据源初始化

    在第三节中已经拿到了多数据源的配置信息,并且被我们解析为了一个易用性很强的配置类
    MultiPersistenceProperties,那么本节将介绍如何完成数据源的初始化,也就是如何创建数据源的bean并注册到Spring容器中。

    首先回到
    LeeMultiPersistenceConfiguration实现的**registerBeanDefinitions() **方法,如下所示。

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 拿到多数据源配置类MultiPersistenceProperties
        MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
        List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
        for (String persistenceName : persistenceNames) {
            // 注册每个数据源对应的数据源bean到Spring容器中
            registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
            registerSqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
            registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        }
    }
    

    跟进**registerDatasource() **方法,如下所示。

    private void registerDatasource(BeanDefinitionRegistry registry,
                                    String persistenceName,
                                    DataSourceProperties dataSourceProperties) {
        // 拿到具体数据源对应的BeanDefinitionBuilder
        // 如果没有配置数据源类型则默认是HikariCP
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(
                ObjectUtils.isNotEmpty(dataSourceProperties.getType()) ? dataSourceProperties.getType() : DEFAULT_DATASOURCE_CLASS);
    
        // 创建数据源的BeanDefinition并完成注册
        registry.registerBeanDefinition(persistenceName, beanDefinitionBuilder.getBeanDefinition());
    }
    

    这里的注册bean实际就是注册BeanDefinition,依赖BeanDefinitionBuilder来创建对应数据源的BeanDefinition,注意到这里好像仅仅只是将数据源的BeanDefinition创建出来然后就注册到BeanDefinitionRegistry中了,并没有进行一些数据源的属性相关的设置,那么数据源的属性是怎么被设置的呢,还记得在第二节中我们通过
    LeeMultiPersistenceAutoConfiguration导入了一个叫做
    DataSourceBeanPostProcessorbean后置处理器吗,数据源的属性的设置就是在这个后置处理器中完成的,下面一起看一下。

    public class DataSourceBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            // 不同的数据源类型走不同的逻辑
            if (bean instanceof HikariDataSource) {
                assembleHikariDataSource((HikariDataSource) bean, beanName);
            } else if (bean instanceof DruidDataSource) {
                assembleDruidDatasource((DruidDataSource) bean, beanName);
            } else if (bean instanceof DataSource) {
                assembleTomcatJdbcDatasource((DataSource) bean, beanName);
            }
            return bean;
        }
    
        private void assembleHikariDataSource(HikariDataSource dataSource, String persistenceName) {
            Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                    .getPersistenceDatasourceProperties(persistenceName);
            dataSource.setJdbcUrl(persistenceDatasourceProperties.get(KEY_URL));
            Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
            binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
        }
    
        private void assembleDruidDatasource(DruidDataSource dataSource, String persistenceName) {
            Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                    .getPersistenceDatasourceProperties(persistenceName);
            Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
            binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
        }
    
        private void assembleTomcatJdbcDatasource(DataSource dataSource, String persistenceName) {
            Map<String, String> persistenceDatasourceProperties = LeeMultiPersistenceConfiguration
                    .getPersistenceDatasourceProperties(persistenceName);
            Binder binder = new Binder(new MapConfigurationPropertySource(persistenceDatasourceProperties));
            binder.bind(StringUtils.EMPTY, Bindable.ofInstance(dataSource));
        }
    
    }
    
    // 在加载并解析数据源配置的时候对配置信息做了缓存
    public class LeeMultiPersistenceConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    
        ......
    
        private static Map<String, Map<String, Map<String, String>>> persistencePropertiesCache;
    
        public static Map<String, String> getPersistenceDatasourceProperties(String persistenceName) {
            Map<String, Map<String, String>> persistenceProperties = persistencePropertiesCache.get(persistenceName);
            return persistenceProperties.get(KEY_DATASOURCE);
        }
    
    }
    


    DataSourceBeanPostProcessor中,仅针对类型为HikariDataSourceDruidDataSourceDataSourcebean生效,然后针对这些bean基于Binder完成属性设置。因为在
    LeeMultiPersistenceConfiguration中加载数据源的配置时已经对数据源配置信息做了缓存,所以现在可以直接通过
    LeeMultiPersistenceConfiguration拿到某个数据源对应的配置信息。

    那么至此,完整的数据源bean就注册好了。

    五. MyBatis初始化

    同样先回到
    LeeMultiPersistenceConfiguration实现的**registerBeanDefinitions() **方法,如下所示。

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 拿到多数据源配置类MultiPersistenceProperties
        MultiPersistenceProperties multiPersistenceProperties = parseMultiPersistenceProperties();
        List<String> persistenceNames = multiPersistenceProperties.getPersistenceNames();
        for (String persistenceName : persistenceNames) {
            registerDatasource(registry, persistenceName, multiPersistenceProperties.getDataSourceProperties(persistenceName));
            // 注册每个数据源对应的SqlSessionFactory到Spring容器中
            registerSqlSessionFactory(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
            // 注册每个数据源对应的MapperScannerConfigurer到Spring容器中
            registerMapperScannerConfigurer(registry, persistenceName, multiPersistenceProperties.getMybatisProperties(persistenceName));
        }
    }
    

    现在先看一下**registerSqlSessionFactory() **方法,如下所示。

    private void registerSqlSessionFactory(BeanDefinitionRegistry registry,
                                           String persistenceName,
                                           MybatisExtendProperties mybatisProperties) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(SqlSessionFactoryBean.class);
        // 为SqlSessionFactory添加数据源
        // 主要就是指定数据源的名字
        beanDefinitionBuilder.addPropertyReference(DATA_SOURCE, persistenceName);
        // 设置SqlSessionFactory的配置文件路径
        beanDefinitionBuilder.addPropertyValue(CONFIG_LOCATION, mybatisProperties.getConfigLocation());
        registry.registerBeanDefinition(BeanNameUtil.getSqlSessionFactoryName(persistenceName),
                beanDefinitionBuilder.getBeanDefinition());
    }
    

    上述方法也是通过BeanDefinitionBuilder来完成SqlSessionFactory对应的BeanDefinition的创建,属性设置和注册。有两点需要注意。

    实际注册的是SqlSessionFactoryBeanBeanDefinitionSqlSessionFactoryBean提供了更多丰富的配置来完成SqlSessionFactory的创建,例如可以设置引用的数据源名称以及MyBatis的配置文件路径等;
    注册的SqlSessionFactory的名字格式是固定的且为dataSourceName + SqlSessionFactory。这样是为了方便MapperScannerConfigurer引用。
    现在继续看
    **registerMapperScannerConfigurer() **方法,如下所示。

    private void registerMapperScannerConfigurer(BeanDefinitionRegistry registry,
                                                 String persistenceName,
                                                 MybatisExtendProperties mybatisProperties) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(MapperScannerConfigurer.class);
        // 设置SqlSessionFactory
        beanDefinitionBuilder.addPropertyValue(SQL_SESSION_FACTORY_BEANNAME, BeanNameUtil.getSqlSessionFactoryName(persistenceName));
        // 设置映射接口的包路径
        beanDefinitionBuilder.addPropertyValue(BASE_PACKAGE, mybatisProperties.getBasePackage());
        registry.registerBeanDefinition(BeanNameUtil.getMapperScannerConfigurerName(persistenceName),
                beanDefinitionBuilder.getBeanDefinition());
    }
    

    其实和注册SqlSessionFactory是一样的方式,唯一需要注意的就是在上述方法中为数据源对应的MapperScannerConfigurer设置了SqlSessionFactory以及映射接口的路径。

    至此,MyBatis的初始化就做完了,其实就是向Spring容器注册每个数据源对应的SqlSessionFactorybean以及MapperScannerConfigurerbean

    六. Springboot数据源原生自动装配抑制

    由于我们自己定义了数据源的相关配置格式,那么相应的用户就不需要再去提供类似于spring.datasource这样的配置,所以我们需要抑制Springboot的数据源的原生自动装配的执行,依赖的扩展点是
    ApplicationContextInitializer。

    如果熟悉Springboot的自动装配,那么肯定对
    AutoConfigurationImportSelector不陌生,这个类的**getAutoConfigurationEntry() **方法会拿到所有自动装配的配置类的全限定名,如下所示。

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 获取@EnableAutoConfiguration注解的元数据属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 将需要自动装配的组件的配置类的全限定名获取出来
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 去除重复的组件
        configurations = removeDuplicates(configurations);
        // 去除被排除的组件
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 去除依赖项不满足的组件
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回剩余的需要自动装配的组件的配置类的全限定名
        return new AutoConfigurationEntry(configurations, exclusions);
    }
    

    其中**getExclusions() **方法会拿到需要排除的自动装配组件的全限定名,如下所示。

    protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        Set<String> excluded = new LinkedHashSet<>();
        excluded.addAll(asList(attributes, "exclude"));
        excluded.addAll(asList(attributes, "excludeName"));
        excluded.addAll(getExcludeAutoConfigurationsProperty());
        return excluded;
    }
    
    protected List<String> getExcludeAutoConfigurationsProperty() {
        Environment environment = getEnvironment();
        if (environment == null) {
            return Collections.emptyList();
        }
        if (environment instanceof ConfigurableEnvironment) {
            Binder binder = Binder.get(environment);
            // PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE为spring.autoconfigure.exclude
            return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
                    .orElse(Collections.emptyList());
        }
        String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
        return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
    }
    

    在获取需要排除的自动装配的组件的全限定名时,实际就是去Environment中通过
    spring.autoconfigure.exclude拿到需要排除的组件的全限定名,那么现在找到切入点了,只要在getExclusions() 方法执行之前向Environment添加
    spring.autoconfigure.exclude的配置,那么就能够排除指定自动装配类的执行,那么最合适的扩展点其实就是
    ApplicationContextInitializer,理由如下。

    1. ApplicationContextInitializer的加载在初始化SpringApplication时就已经完成;
    2. ApplicationContextInitializer的执行是在prepareContext() 即准备容器的时候,
      这个时候
      Environment
      已经加载完毕,并且getExclusions() 方法也还没执行。
      所以现在我们在
      spring.factories
      文件中加入如下内容。
    org.springframework.context.ApplicationContextInitializer=\
      com.lee.multidatasource.initializer.ExcludeInitializer
    

    然后ExcludeInitializer实现如下。

    public class ExcludeInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
        private static final String EXCLUDE_PROPERTY_SOURCE_NAME = "EXCLUDE_PROPERTY_SOURCE_NAME";
    
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            ConfigurableEnvironment environment = applicationContext.getEnvironment();
    
            Properties properties = new Properties();
            properties.setProperty("spring.autoconfigure.exclude",
                    "org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration");
    
            environment.getPropertySources().addLast(new PropertiesPropertySource(
                    EXCLUDE_PROPERTY_SOURCE_NAME, properties));
        }
    
    }
    

    至此,就完成了Springboot数据源原生自动装配的抑制。

    总结

    本文对MyBatis多数据源Starter的实现进行了说明,思维导图如下所示。

    源码可以在如下链接进行下载。

    multidatasource GitHub

    相关文章

      网友评论

        本文标题:一文搞懂MyBatis多数据源Starter实现

        本文链接:https://www.haomeiwen.com/subject/tnvfjdtx.html