共计 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 使得 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. 一个相干的小问题
如果 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…