springBoot 启动文件为什么要排除DataSourceAutoConfiguration

本文深入剖析了Spring Boot的DataSourceAutoConfiguration源码,详细解释了如何自动配置单数据源,以及为何需要排除该配置以实现多数据源。内容包括DataSourceProperties的配置细节,如数据库连接属性、初始化过程以及如何自定义数据源。同时,介绍了EmbeddedDatabaseConfiguration和PooledDataSourceConfiguration的作用,以及如何通过DataSourceBuilder创建和管理数据源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码如下:

@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();
}
<think>好的,我现在需要解决用户的问题:Spring Boot启动时未加载数据源。用户提供的引用内容中,引用了Spring Boot和Spring Cloud的版本信息,以及一些配置示例,但可能没有直接关于数据源的配置。不过,我可以根据常见的解决步骤来思考。 首先,用户提到使用的是Spring Boot 2.2.2.RELEASE和Spring Cloud Hoxton.SR12。这可能涉及依赖管理的问题。例如,是否引入了正确的数据源依赖,比如spring-boot-starter-data-jpa或spring-boot-starter-jdbc。如果没有正确配置,Spring Boot可能不会自动配置数据源。 接下来,用户提供的引用中有关于配置文件的内容,比如开发环境的配置和激活的profile。这提示我需要检查application.properties或application.yml中的配置是否正确。例如,数据库URL、用户名、密码是否正确,数据源相关的属性如spring.datasource.url是否拼写正确。此外,是否有多个配置文件导致配置被覆盖或未生效?比如,是否在application-dev.properties中配置了数据源,但没有激活对应的profile。 另一个可能性是数据源配置被Spring Cloud的某些组件覆盖了。例如,如果使用了服务发现或者配置中心,可能需要检查是否在配置中心中有错误的数据源配置,或者配置未被正确加载。用户提到的引用[1]提到重新创建工程,可能存在依赖缺失或版本冲突,导致自动配置失败。 此外,用户可能没有在启动类上添加必要的注解,比如@SpringBootApplication,或者排除了数据源相关的自动配置类。例如,如果使用了@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}),就会禁用数据源的自动配置。 还有,数据库驱动的问题。例如,MySQL的驱动类是否正确,是否在pom.xml或build.gradle中添加了对应的驱动依赖,并且版本与数据库兼容。比如,MySQL 8.x需要使用com.mysql.cj.jdbc.Driver,并且连接URL需要指定时区参数。 最后,查看启动日志中的错误信息是关键。例如,是否有“Failed to configure a DataSource”这样的错误,提示找不到合适的驱动类,或者连接被拒绝。根据日志中的异常堆栈,可以更准确地定位问题。 总结可能的解决步骤: 1. 检查依赖是否正确添加。 2. 验证配置文件中的数据库连接信息。 3. 确保没有排除数据源自动配置。 4. 检查数据库驱动是否正确配置。 5. 查看启动日志以获取具体错误信息。</think>### Spring Boot启动时未加载数据源的解决方案 #### 1. 检查数据源依赖 确保项目中已添加数据库驱动和Spring Boot数据源依赖: ```xml <!-- MySQL驱动示例 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- JDBC Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> ``` 若使用JPA,需包含`spring-boot-starter-data-jpa`。未正确引入依赖会导致自动配置失败[^1]。 #### 2. 验证数据源配置 检查`application.properties`或`application.yml`中的配置: ```properties # MySQL示例(注意时区参数) spring.datasource.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` - 确认配置文件位于`src/main/resources` - 确保未使用错误的Profile配置(如未激活的`application-dev.properties`)[^2] #### 3. 检查自动配置排除启动类中,确保未排除数据源自动配置: ```java // 错误示例:手动排除会导致数据源失效 @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) ``` 移除`exclude`参数或检查是否有其他配置干扰。 #### 4. 数据库驱动兼容性 - MySQL 8.x需使用`com.mysql.cj.jdbc.Driver`而非旧版`com.mysql.jdbc.Driver` - 检查驱动版本与数据库版本匹配(如MySQL 5.7与8.x驱动不兼容) #### 5. 分析启动日志 查看控制台输出的错误类型: - **"Failed to configure a DataSource"**:通常表示配置缺失或依赖未引入 - **"Connection refused"**:数据库服务未启动或网络不通 - **"Access denied"**:用户名/密码错误 #### 6. 多数据源配置问题 若配置了多个数据源,需通过`@Primary`注解指定主数据源: ```java @Bean @Primary @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } ``` #### 7. 版本冲突检查 确认Spring Boot与Spring Cloud版本兼容(如用户使用的Hoxton.SR12对应Boot 2.2.x~2.3.x[^1]),通过`mvn dependency:tree`排查依赖冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半夏_2021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值