一文搞懂MyBatis多数据源Starter实现
作者:半夏之沫 发布时间:2023-07-19 03:34:22
前言
本文将实现一个MyBatis的Springboot的Starter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成MyBatis多数据源的初始化和使用,相较于MyBatis官方的Starter包,扩展了多数据源的使用场景。
本文的所有源码可以从如下仓库下载。
multidatasource GitHub
Springboot版本:2.7.6
一. 实现思路
要实现Starter包,肯定需要借助Springboot的自动装配机制,所以我们首先需要提供自动装配的配置类。
然后我们需要加载多个数据源的配置并且生成对应的数据源,同时还需要可以根据用户配置的type创建不同的数据源,例如可以支持创建HikariCP,Druid和TomcatJdbc的数据源。
创建出来的数据源需要根据用户的配置,设置给不同的SqlSessionFactory,然后不同的SqlSessionFactory设置给不同的MapperScannerConfigurer,最终实现的效果就是一部分映射接口使用一个数据源,另一部分映射接口使用另一个数据源。
最后,还需要提供一种手段,抑制Springboot原生的数据源加载,这个功能我们可以通过ApplicationContextInitializer这个扩展点来完成。
整体的一个思维导图如下所示。
二. 自动装配实现
由于适配的是Springboot的2.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,用于加载配置以及创建数据源和MyBatis的bean,另一个是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的下一级的配置,是数据源的名字,可以由用户自定义,这个名字最终会作为数据源的bean在IOC容器中的名字。
在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这个配置前缀已经是确定的,所以在Binder的bind() 方法中传入的配置名就是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中仅提供了对数据源配置Map的set方法,然后提供了多个有具体含义的get方法,例如拿到数据源的个数,名字集合以及某个数据源的DataSourceProperties或者MybatisExtendProperties,当然,如何将某个数据源对应的配置的Map解析为DataSourceProperties和MybatisExtendProperties,还是依靠的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;
}
在完成所有数据源配置加载并且生成包装类后,我们做的事情就是遍历每一个数据源的名字,然后通过数据源名字从包装类中拿到对应的DataSourceProperties和MybatisExtendProperties,最后添加到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中也进行了适当充血,首先将DataSourceProperties和MybatisExtendProperties封装为了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导入了一个叫做DataSourceBeanPostProcessor的bean后置处理器吗,数据源的属性的设置就是在这个后置处理器中完成的,下面一起看一下。
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中,仅针对类型为HikariDataSource,DruidDataSource或DataSource的bean生效,然后针对这些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的创建,属性设置和注册。有两点需要注意。
实际注册的是SqlSessionFactoryBean的BeanDefinition。SqlSessionFactoryBean提供了更多丰富的配置来完成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容器注册每个数据源对应的SqlSessionFactory的bean以及MapperScannerConfigurer的bean。
六. 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,理由如下。
ApplicationContextInitializer的加载在初始化SpringApplication时就已经完成;
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数据源原生自动装配的抑制。
来源:https://juejin.cn/post/7222666499663839293
猜你喜欢
- 今天给大家介绍一下SpringBoot中JPA的一些常用操作,例如:增删改查、分页、排序、事务操作等功能。下面先来介绍一下JPA中一些常用的
- 前言之前用简书的时候一直是在web端,后来下载了客户端,看到了搜索的那个动画,就尝试的去写了,没写之前感觉挺容易的,写了之后,就感觉里面还是
- 各位亲们可以尝试以下代码:注:这里我就只有一个html标签对来说明问题了,首部之类的东西,自己添加。<html> &n
- 【程序1】题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?1.程序分析:可填在百位、十位、个位的数字都是1
- 最近在看《.NET游戏编程入门经典 C#篇》 第一章介绍了如何制作俄罗斯方块,自己试了试按照书上的步骤,可算是完成了。于是写下这篇文章留作纪
- 前言当系统的并发比较高的时候,日志的处理输出也是一种性能的开销负担,所以,选择一个中间件来处理消费日志必不可少!下面是spring boot
- 什么是JMMJMM全称Java Memory Model, 中文翻译Java内存模型,一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问
- 先看看效果Like This↓一、公共WiFi 公用电脑什么的在我们日常在线上工作、玩耍时,不论开电脑、登录淘宝、玩网游统统都会用到键盘输入
- 一、导航栏UINavigationBar1、导航栏的使用在iOS开发中,我们通常会使用导航控制器,导航控制器中封装了一个UINavigati
- 静态库和动态库的区别1、静态库的扩展名一般为".a"或者".lib";动态库的扩展名一般为"
- 在我们实现某些功能时,可能会有倒计时的需求。比如发送短信验证码,发送成功后可能要求用户一段时间内不能再次发送,这时候我们就需要进行倒计时,时
- 前言RefreshIndicator是Flutter里常见的下拉刷新组件,使用是比较方便的。但由于产品兄弟对其固定的刷新样式很是不满,而且代
- 前言 因为自己在做的一个小软件里面需要用到从A-Z排序的ListView,所以自然而然的想到了微信的联系人,我想要的就是那样的效果。本来没
- 首先给出一段代码:public class AslistMethod { public static void main(String[]
- 今天讲解一下Fragment的控制,主要是切换View和页面替换等操作。还有就是如何获取Fragment的管理对象,以及与Activity的
- iOS定位 - 普通定位(没有地图) - 反地理编码(得到具体位置),下面通过代码给大家详解,代码如下:#import <CoreLo
- 效果展示在实际项目当中我们经常看到如下各种剪裁形状的效果,Flutter 为我们提供了非常方便的 Widget 很轻松就可以实现,下面我们来
- 废话开篇:iOS与android在实现列表界面的时候是有重用机制的,目的就是减少内存开销,用时间换空间。个人感觉flutter并没有特别强调
- 本文实例讲述了C++实现的O(n)复杂度内查找第K大数算法。分享给大家供大家参考,具体如下:题目:是在一组数组(数组元素为整数,可正可负可为
- 起源flutter作为一个跨平台的框架,在绘制上体现出了它跨平台的良好性能.那么,它是如何从runApp()后 绘制上屏的呢?本文将与你一起