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文件

关上这个文件,能够看到其中配置了org.springframework.boot.autoconfigure.EnableAutoConfiguration

这是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.HikariDataSourceorg.apache.tomcat.jdbc.pool.DataSourceorg.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使得DruidStatPropertiesDataSourceProperties中的配置失效,点进去就能发现它们的配置前缀:

@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. 一个相干的小问题

如果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
@Configurationpublic 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());    }}

运行后果:

能够看到除了最初的name属性,其余的属性都还是application.yaml中配置的值。

参考鸣谢

内置数据库和池化数据源:https://developer.51cto.com/a...