关于springboot:SpringBoot数据源注入原理

41次阅读

共计 5373 个字符,预计需要花费 14 分钟才能阅读完成。

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.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 使得 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
@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());
    }
}

运行后果:

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

参考鸣谢

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

正文完
 0