代码如下:

@SpringBootApplication(scanBasePackages = "com.xen",exclude = DataSourceAutoConfiguration.class)

exclude 排除AutoConfig, 禁止 SpringBoot 自动注入数据源。

为什么要排除呢?因为项目需要用到多数据源。所以需要排除。

下面介绍 DataSourceAutoConfiguration的源码。

1.DataSourceAutoConfiguration概况

DataSourceAutoConfiguration.class 会自动查找 application.yml 或者 properties 文件里的 spring.datasource.* 相关属性并自动配置单数据源「注意这

里提到的单数据源」。作用如下:

  • 初始化 DataSourceProperties配置文件
  • 初始化数据源
  • 执行sql文件
  • 为数据源注册一个DataSourcePoolMetadataProvider实例

2.源码分析

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
// 从配置文件中映射 DataSource 的值
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
      DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

   @Configuration
   //判断是否引入 内置数据库:H2,DERBY,HSQL
   @Conditional(EmbeddedDatabaseCondition.class)
   //如果这是没有DataSource/XADataSource 对应的 BeanDefinition,就通过导入 EmbeddedDataSourceConfiguration.class 来,配置内置数据库对应的数据源!!
   @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
   @Import(EmbeddedDataSourceConfiguration.class)
   protected static class EmbeddedDatabaseConfiguration {}

 
   @Configuration
   //判断是否引入依赖的数据源:HikariDataSource、tomcat.jdbc.pool.DataSource、BasicDataSource
   @Conditional(PooledDataSourceCondition.class)
   //如果这是没有DataSource/XADataSource 对应的 BeanDefinition,就通过以下属性的配置文件,配置数据源!!
   //配置数据源的时候,如果没有指定一些数据库的参数,就会报错哦
   @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
   @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
         DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
         DataSourceJmxConfiguration.class })
   protected static class PooledDataSourceConfiguration {}
   
   static class PooledDataSourceCondition extends AnyNestedCondition {}
   static class EmbeddedDatabaseCondition extends SpringBootCondition {}
}

(1) 初始化 DataSourceProperties 配置文件

如果设置的不是内置数据库的话:
1)必须配置的有:url,username,password
2)数据库名不是必须的
3)driverClassName 不是必须的:可以从 url 中推导出
4)type 不是必须的:可以从上下文中推导出
如果设置的是内置数据库的话:
1)必须配置的有:引入内置数据库依赖,如:H2
2)其它所有的都可以不配置(有默认的配置),如果随意配置,可能产生冲突

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
	private ClassLoader classLoader;
    //数据库名:如果使用内置数据库,默认为testdb
	private String name;
	//Whether to generate a random datasource name
	private boolean generateUniqueName;
    //如果generateUniqueName==true,则不使用name,而使用uniqueName来做数据库名
    private String uniqueName;
    //完整的数据库连接池名。默认从项目中检测出
	private Class<? extends DataSource> type;
    //JDBC driver的完整名,默认从URL中检测出相对应的driver
	private String driverClassName;
    //JDBC URL of the database
	private String url;
    
	//Login username of the database
	private String username;
	//Login password of the database
	private String password;
    //JNDI 数据源的位置:如果指定了,则数据库连接数据将会失效:driverClassName,url,username,password
	private String jndiName;
	//初始化database使用的sql文件的模式,默认是EMBEDDED,如果是NONE就不会执行sql文件
    //如果设置的模式和检测出来的模式不匹配,也不会执行sql文件
	private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;

    //执行sql文件相关,schema-${platform}.sql,data-${platform}.sql
    //默认执行不带 platform 的 sql 文件 + 带 platform 的 sql 文件
	private String platform = "all";
    //具体的 schema 文件的位置,如果指定了这个就不会查找默认的sql文件了
	private List<String> schema;
	//执行schema使用数据库的用户名
	private String schemaUsername;
	//执行schema使用数据库的密码,如果schemaUsername和schemaPassword都不指定,就使用 **主数据源** 作为执行目的数据库!
	private String schemaPassword;
	//同schema
	private List<String> data;
	private String dataUsername;
	private String dataPassword;
    
    //如果初始化database时报错,是否继续
	private boolean continueOnError = false;
	//Statement separator in SQL initialization scripts.
	private String separator = ";";
	//SQL scripts encoding.
	private Charset sqlScriptEncoding;
	//默认的内置数据库连接信息:
    //1 NONE(null, null, null)
    //2 H2(EmbeddedDatabaseType.H2, "org.h2.Driver","jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
    //3 DERBY(...)
    //4 HSQL(...)
	private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;

	private Xa xa = new Xa();
	//从项目中匹配相应的内置数据库,查找是否引入了相应的依赖,如果引入了H2依赖,这里embeddedDatabaseConnection就设置成 H2
	@Override
	public void afterPropertiesSet() throws Exception {
		this.embeddedDatabaseConnection = EmbeddedDatabaseConnection
				.get(this.classLoader);
	}

    // 通过 spring.datasource 属性 初始化一个DataSourceBuilder,用来方便的创建datasource,就是一个封装的方法
	public DataSourceBuilder<?> initializeDataSourceBuilder() {
		return DataSourceBuilder.create(getClassLoader()).type(getType())
				.driverClassName(determineDriverClassName()).url(determineUrl())
				.username(determineUsername()).password(determinePassword());
	}
	//智能的获取DriverClassName
	public String  determineDriverClassName() {
		if (StringUtils.hasText(this.driverClassName)) {
			Assert.state(driverClassIsLoadable(),
					() -> "Cannot load driver class: " + this.driverClassName);
			return this.driverClassName;
		}
		String driverClassName = null;
		if (StringUtils.hasText(this.url)) {
			driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
		}
        //如果走到这,还没识别出 driverClassName,且它为null,就去内置数据库中找匹配的
        //如果项目中没有引入 内置数据库依赖,那就会报错啦
		if (!StringUtils.hasText(driverClassName)) {
			driverClassName = this.embeddedDatabaseConnection.getDriverClassName();
		}
		if (!StringUtils.hasText(driverClassName)) {
			throw new DataSourceBeanCreationException(
					"Failed to determine a suitable driver class", this,
					this.embeddedDatabaseConnection);
		}
		return driverClassName;
	}
    private boolean driverClassIsLoadable() {
		try {
			ClassUtils.forName(this.driverClassName, null);
			return true;
		}
		catch (UnsupportedClassVersionError ex) {
			// Driver library has been compiled with a later JDK, propagate error
			throw ex;
		}
		catch (Throwable ex) {
			return false;
		}
	}
	//Determine the url to use based on this configuration and the environment.
	public String determineUrl() {
		if (StringUtils.hasText(this.url)) {
			return this.url;
		}
		String databaseName = determineDatabaseName();
		String url = (databaseName != null)
				? this.embeddedDatabaseConnection.getUrl(databaseName) : null;
		if (!StringUtils.hasText(url)) {
			throw new DataSourceBeanCreationException(
					"Failed to determine suitable jdbc url", this,
					this.embeddedDatabaseConnection);
		}
		return url;
	}
	//Determine the name to used based on this configuration.
	public String determineDatabaseName() {
		if (this.generateUniqueName) {
			if (this.uniqueName == null) {
				this.uniqueName = UUID.randomUUID().toString();
			}
			return this.uniqueName;
		}
		if (StringUtils.hasLength(this.name)) {
			return this.name;
		}
		if (this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE) {
			return "testdb";
		}
		return null;
	}
	//
	public String determineUsername() {
		if (StringUtils.hasText(this.username)) {
			return this.username;
		}
		if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
			return "sa";
		}
		return null;
	}

	public String determinePassword() {
		if (StringUtils.hasText(this.password)) {
			return this.password;
		}
		if (EmbeddedDatabaseConnection.isEmbedded(determineDriverClassName())) {
			return "";
		}
		return null;
	}
	//XA Specific datasource settings.
	public static class Xa {
		//XA datasource fully qualified name.
		private String dataSourceClassName;
		//Properties to pass to the XA data source.
		private Map<String, String> properties = new LinkedHashMap<>();
    }
}

(2) 引入 EmbeddedDatabaseConfiguration 配置类

//所有的 condition 类都会最终继承 SpringBootCondition,SpringBootCondition 是一个模板类,继承它后,我们只需要实现核心的 getMatchOutCome() 方法来自定义一个 Condition 类了。当这个类被@Conditional 注解引入的时候,最终时候执行这个核心方法来判断是否匹配的
static class EmbeddedDatabaseCondition extends SpringBootCondition {
	private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		ConditionMessage.Builder message = ConditionMessage
				.forCondition("EmbeddedDataSource");
	    // anyMatches() 就是一个 SpringbootCondition 类中的模板方法,意思是:匹配任意一个 pooledCondition 中的条件
	    // 这里 pooledCondition 中的条件其实是匹配非内置数据库的条件,这就很奇怪了,为什么不把 匹配非内置数据库的配置放在前面呢?
		if (anyMatches(context, metadata, this.pooledCondition)) {
			return ConditionOutcome
					.noMatch(message.foundExactly("supported pooled data source"));
		}
		//这里查找了项目中有没有引入 H2,DERBY,HSQL 这3个class,如果没有引入,就返回 null,引入了返回 第一个 type
		EmbeddedDatabaseType type = EmbeddedDatabaseConnection
				.get(context.getClassLoader()).getType();
		if (type == null) {
			return ConditionOutcome
					.noMatch(message.didNotFind("embedded database").atAll());
		}
		return ConditionOutcome.match(message.found("embedded database").items(type));
	}
}

(3) 引入 PooledDataSourceConfiguration 配置类

@Configuration
//满足其中的任意一个:1)有spring.datasource.type属性 2)满足PooledDataSourceAvailableCondition:项目中引入了数据源依赖
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
//如果满足上面条件,就解析一下几个配置类(注意顺序,hikari优先)
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
      DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
      DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {}

 //继承了 AnyNestedCondition 的类,会对这个类中的所有内部类(不一定非得是静态内部类)上的注解做匹配,只要其中有一个匹配了,就匹配了
//说明:如果没有spring.datasource.type属性,就默认查看项目中有没有引入:hikari,tomcat,dbcp2。这样说明如果项目中exclude了这3个,那么就必须使用 spring.datasource.type来指定数据库连接池了
//type 属性优先级比较低,是在找不到,就通过 DataSourceConfiguration.Generic.class 类,来根据 type 属性配置
static class PooledDataSourceCondition extends AnyNestedCondition {
   PooledDataSourceCondition() {
      //因为 AnyNestedCondition 实现了 ConfigurationCondition,所以要设置 这个属性
      //这个属性在 sholudSkip() 方法中会用到,如果这个属性是 REGISTER_BEAN 的话,在生成 configClass 阶段就不会进行匹配过滤,要等到 loadBeanDefintion 的时候,在进行过滤
      //因为类中的静态内部类,都被 @ConditionalOnProperty 注解,这些注解都是在 configClass 阶段做匹配的,所以要设置为 PARSE_CONFIGURATION
      //如果这里设置为 REGISTER_BEAN,但是内部有应该在 configClass 阶段做匹配的,就不符合整体思想了(这样本应该在 configClass 阶段就做匹配的,延迟到了 loadBeanDefintion 阶段),就可能能出现莫名其妙的问题。
      //进一步思考:继承了 AnyNestedCondition 的子类中,不应该同时存在 configClass 阶段做匹配和在 loadBeanDefintion 阶段匹配的
      super(ConfigurationPhase.PARSE_CONFIGURATION);
   }
   //条件一:是否配置了 spring.datasource.type 属性
   @ConditionalOnProperty(prefix = "spring.datasource", name = "type")
   static class ExplicitType {
   }
   //条件二:项目中是否引入了数据源依赖(如,hikari)
   @Conditional(PooledDataSourceAvailableCondition.class)
   static class PooledDataSourceAvailable {
   }
}
static class PooledDataSourceAvailableCondition extends SpringBootCondition {
  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context,
        AnnotatedTypeMetadata metadata) {
     //这个类只是用来传递消息的
     ConditionMessage.Builder message = ConditionMessage
           .forCondition("PooledDataSource");
      //getDataSourceClassLoader(context):内部做class.forName来找项目中的相关class,找到了就不为null啦,一般肯定能找到的,在org.springframework.boot:spring-boot-starter-jdbc中就已经引入了 hikariDatabase,而在 spring.boot:mybatis-spring-boot-starter中引入了 jdbc!
     if (getDataSourceClassLoader(context) != null) {
        return ConditionOutcome
              .match(message.foundExactly("supported DataSource"));
     }
     return ConditionOutcome
           .noMatch(message.didNotFind("supported DataSource").atAll());
  }
  private ClassLoader getDataSourceClassLoader(ConditionContext context) {
      //在DataSourceBuilder中有个关键的 findType方法来按:hikari,tomcat,dbcp2顺序查找,一查到就返回
     Class<?> dataSourceClass = DataSourceBuilder
           .findType(context.getClassLoader());
     return (dataSourceClass != null) ? dataSourceClass.getClassLoader() : null;
  }
}

(4) 导入 DataSourcePoolMetadataProvidersConfiguration 配置类

//为数据源注册一个DataSourcePoolMetadataProvider实例,这个实例主要用于获取内置数据源的一些状态
@Configuration
public class DataSourcePoolMetadataProvidersConfiguration {
	@Configuration
	@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
	static class TomcatDataSourcePoolMetadataProviderConfiguration {...}
	@Configuration
	@ConditionalOnClass(HikariDataSource.class)
	static class HikariPoolDataSourceMetadataProviderConfiguration {
		@Bean
		public DataSourcePoolMetadataProvider hikariPoolDataSourceMetadataProvider() {
			return (dataSource) -> {
				HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource,
						HikariDataSource.class);
				if (hikariDataSource != null) {
					//这里就返回了一个HikariDataSourcePoolMetadata实例,算是代理数据源吧
					return new HikariDataSourcePoolMetadata(hikariDataSource);
				}
				return null;
			};
		}
	}
	@Configuration
	@ConditionalOnClass(BasicDataSource.class)
	static class CommonsDbcp2PoolDataSourceMetadataProviderConfiguration {...}
}
//函数式接口,可以这样声明
//DataSourcePoolMetadataProvider dpmp = dataSource->{...,return 一个DataSourcePoolMetadata实例}
@FunctionalInterface
public interface DataSourcePoolMetadataProvider {
	//返回能够管理指定数据源的DataSourcePoolMetadata实例,如果无法处理给定的数据源,则返回空值。
	DataSourcePoolMetadata getDataSourcePoolMetadata(DataSource dataSource);
}
//每个都可能返回null,如果数据源没有提供相应的信息
public interface DataSourcePoolMetadata {
	//返回池的使用情况,值介于0和1之间(如果池不受限制,则返回-1),1表示所有的都分配了
	Float getUsage();
	//返回从数据源分配的当前活动连接数
	Integer getActive();
	//返回可同时分配的最大活动连接数,无限制的话返回-1
	Integer getMax();
	//返回池中空闲连接的最小数目
	Integer getMin();
	//返回用于验证连接是否有效的查询
	String getValidationQuery();
	//此池创建的连接的默认自动提交状态
	Boolean getDefaultAutoCommit();
}

(5) 如何自定义数据源

  • 每个数据源都有自己的属性,
  • 通过 DataSourceBuilder 类来创建数据源实例
  • 通过 @ConfigurationProperties() 注解,将配置文件中的属性,映射到相应的数据源实例中
  • DataSourcePoolMetadataProvidersConfiguration 中只配置了默认的3个数据源,如果需要,需要自己定义它
@Bean(name = "druidDatasource")
@ConfigurationProperties("wang-kang.druid.datasource")
public DataSource druidDatasource() {
    return DataSourceBuilder.create().type(DruidDataSource.class).build();
}