共计 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…