SpringBoot数据源注入原理

1. SpringBoot数据源注入原理 我们知道,在application.yaml中配置spring.datasource之后,就可以对数据源进行注入,可这是为什么呢?

spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root

(1) 默认数据源
在SpringBoot的spring-boot-autoconfigure包中,META-INF文件夹下有一个名为spring.factories文件
SpringBoot数据源注入原理
文章图片

打开这个文件,可以看到其中配置了org.springframework.boot.autoconfigure.EnableAutoConfiguration
SpringBoot数据源注入原理
文章图片

这是SpringBoot自动装配的注解
看到这里应该明白了,spring.factories就是SpringBoot提供的spi
SpringBoot提供了注解,会对这里配置的所有Bean进行自动装配。
在spring.factories中可以找到一条配置:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
这个就是数据源注入的关键。
在DataSourceAutoConfiguration中,可以看到默认支持两种类型的数据源:
EmbeddedDatabaseConfiguration(内嵌数据库)和PooledDataSourceConfiguration(池化数据源)。
@Configuration(proxyBeanMethods = false) @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import(EmbeddedDataSourceConfiguration.class) protected static class EmbeddedDatabaseConfiguration {}@Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration {}

可以看到他们都由@Conditional注解指明了注入条件,详细的代码就不贴了,可以自己去看
池化数据源的条件是要么配置了spring.datasource.type,要么满足PooledDataSourceAvailableCondition的条件,这个条件是要通过以下类的类加载器分别去加载以下几个类:
com.zaxxer.hikari.HikariDataSource org.apache.tomcat.jdbc.pool.DataSource org.apache.commons.dbcp2.BasicDataSource 存在oracle.jdbc.OracleConnection的oracle.ucp.jdbc.PoolDataSourceImpl

内嵌数据库的条件是首先要未配置spring.datasource.url,另外要不满足池化数据源的条件,也就是说会判断是否匹配池化数据源的条件,没有匹配到才会创建内嵌数据库
内嵌数据库支持H2、DERBY、HSQL等几种类型。
(2) Druid数据源
我们在最开始的application.yaml中配置了spring.datasource.type为DruidDataSource,说明使用的是Druid数据源
可以看下DruidDataSourceAutoConfigure中的代码:
@Configuration @ConditionalOnClass(DruidDataSource.class) @AutoConfigureBefore(DataSourceAutoConfiguration.class) @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class}) @Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class}) public class DruidDataSourceAutoConfigure {private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class); @Bean(initMethod = "init") @ConditionalOnMissingBean public DataSource dataSource() { LOGGER.info("Init DruidDataSource"); return new DruidDataSourceWrapper(); } }

@ConditionalOnClass表明存在DruidDataSource的时候,配置才会生效。
@AutoConfigureBefore表明自动配置在DataSourceAutoConfiguration之前生效。
DataSourceAutoConfiguration就是上边提到的数据源自动装配的关键类,说明在SpringBoot默认的数据源装配之前,会优先装配Druid数据源
而DataSourceAutoConfiguration中的@ConditionalOnMissingBean注解说明,如果已经装配了数据源,就不会再装配默认的数据源了
@EnableConfigurationProperties使得DruidStatProperties和DataSourceProperties中的配置生效,点进去就能发现它们的配置前缀:
@ConfigurationProperties("spring.datasource.druid") public class DruidStatProperties { // 省略代码 }@ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean { // 省略代码 }

@ConditionalOnMissingBean表明,只有在容器中没有dataSource的Bean的时候,才会注入Druid数据源。也就是说,如果我们手动注入一个dataSource,则不会再创建Druid的数据源了。
@Import注入了四个Bean,用于实现Druid数据源的监控、统计等其他功能。
@ConditionalOnMissingBean注解表明,如果已经装配了数据源,则不会再装配Druid数据源了
最终,return了一个DruidDataSourceWrapper,通过@Bean注解将Durid数据源注入到容器中。
2. 一个相关的小问题 【SpringBoot数据源注入原理】如果application.yaml中配置了datasource,然后又手动注入了datasource,到底会以哪里的配置为准呢?
先给出结论,如果手动对dataSource这个Bean进行了注入,则只会对application.yaml中未配置的属性生效,其他属性还是以配置值为准。
这是为什么呢?
我们知道创建一个Bean分为两个阶段:实例化和初始化
在实例化dataSource这个Bean的时候(即doCreateBean方法中的createBeanInstance方法), 会设置一次数据源属性,即手动注入时设置的属性。
而在初始化dataSource的时候(即doCreateBean方法中的initializeBean方法),会先调用applyBeanPostProcessorsBeforeInitialization方法,遍历postProcessBeforeInitialization方法实现前处理。
ConfigurationPropertiesBindingPostProcessor方法会去绑定属性,将application.yaml中设置的属性赋值给dataSource。
所以,最终生成的dataSource中的属性值就是application.yaml中配置的属性,以及手动注入时@Bean中未被覆盖的属性。
写一段简单的代码验证一下:
先在application.yaml中配置属性,然后再手动注入dataSource,并修改其中的属性
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: root

@Configuration public class DruidStarterConfig { @Bean @ConfigurationProperties("spring.datasource") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); // application.yaml中没有配置name属性 druidDataSource.setName("aaa"); druidDataSource.setUsername("bbb"); druidDataSource.setPassword("ccc"); druidDataSource.setUrl("ddd"); druidDataSource.setDriverClassName("eee"); return druidDataSource; } }

打印出数据源的属性
@SpringBootTest @RunWith(SpringRunner.class) public class DruidStarterConfigTest {@Resource private DataSource dataSource; @Test public void test() throws SQLException { DruidDataSource druidDataSource = (DruidDataSource) dataSource; System.out.println(druidDataSource.getDriverClassName()); System.out.println(druidDataSource.getUrl()); System.out.println(druidDataSource.getUsername()); System.out.println(druidDataSource.getPassword()); System.out.println(druidDataSource.getName()); } }

运行结果:
SpringBoot数据源注入原理
文章图片

可以看到除了最后的name属性,其他的属性都还是application.yaml中配置的值。
参考鸣谢 内置数据库和池化数据源:https://developer.51cto.com/a...

    推荐阅读