前言
本次探究的过程是因为在学习Shardingsphere时所引起的,在看Shardingsphere的文档中,发现并没有关于在SpringBoot中集成的介绍,这让我很好奇,有两个原因,其一,我记得之前在项目中是有看到关于shardingsphere-jdbc的starter的,其二,针对大部分项目一般都会提供相应的starter供使用,以便简化开发,比如Druid、Mybatis-Plus等。
带着这两个疑问,我查阅了资料,疑问得到了解决,原来Shardingsphere官方在5.3.0
版本之前确实是有提供starter的,后来为了保持yaml配置统一等,不再提供了,可以使用5.1.2
版本提供的原生 JDBC 驱动 ShardingSphereDriver
。
由此引起了我关于SpringBoot自动配置DataSource,及DataSource和Driver原理的兴趣,之前仅是会使用而已,对其原理是一知半解的,借此机会,进行深入的研究。
准备
在Mysql库中建一张表,如下
create table info(
id int(4) not null auto_increment ,
name varchar(32) default null,
age int(2) default null,
primary key(id)
)engine=innodb;
insert into info(name,age) values('zs',10),('ls',20);
JDBC原生使用回顾
pom中引入mysql依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
java实现
public class JdbcTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://192.168.5.100:3306/test?useSSL=false";
String user = "root";
String password = "root1234";
Class.forName(driver);
try (
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
) {
ResultSet resultSet = statement.executeQuery("select id,name,age from info");
while (resultSet.next()){
String result = String.format("id:%s name:%s age:%s"
, resultSet.getInt("id")
, resultSet.getString("name")
, resultSet.getInt("age"));
System.out.println(result);
}
resultSet.close();
}
}
}
SpringBoot自动集成DataSource回顾
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--
这里提前剧透一下,spring-boot-starter-jdbc和其他的starter有些区别,它是一个空包,
并没有任何文件或代码,它的作用是引入了spring-jdbc包和HikariCP包,所以,不引入它,
用spring-jdbc包和HikariCP包代替,效果是一样的
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
application.properties配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.5.100:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=root1234
java实现
@Component
public class TestRunner implements ApplicationRunner {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
List<Map<String, Object>> list = jdbcTemplate.queryForList("select id,name,age from info");
System.out.println(list);
}
}
源码分析
上面两个例子应该是我们大家都学习过的,我们知道,上述例子都是去数据库查数据,但两者的区别很大,但是,最终DataSource也是要获得一个Connection然后去查询的,那这两者是怎么串联起来的呢,接下来,我们就从源码开始进行分析。
SpringBoot自动注入
我们知道SpringBoot自动注入就是因为有spring-boot-autoconfigure包,在spring-boot-autoconfigure的META-INF目录下我们可以找到SpringBoot的配置文件org.springframework.boot.autoconfigure.AutoConfiguration.imports
,里面可以找到org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
。
DataSourceAutoConfiguration类就是DataSource自动注入的入口。
注: 这里我使用的springboot版本是2.7的,在之前的版本DataSourceAutoConfiguration
是在spring.factories中配置的。
DataSourceAutoConfiguration类解读
DataSourceAutoConfiguration类上注解
下面我们来解读一下DataSourceAutoConfiguration上的注解
AutoConfiguration
是SpringBoot2.7版本新出现的注解,是为了区分自动配置和普通配置类,其作用几乎等价于Configuration。
ConditionalOnClass
条件注入,表示在项目中必须有DataSource
和EmbeddedDatabaseType
类才会注入,注意,必须是同时有这两个类才可以(作者在学习时就犯迷糊了,以为引入一个就可以),但是不是必须要注入Spring容器。
DataSource
是jdk中自带的类,肯定会有,而EmbeddedDatabaseType
类位于spring-jdbc
包中,所以,我们必须引入spring-jdbc
依赖,才会自动注入,所以在上文我们引入依赖时,可以拆开引用。
ConditionalOnMissingBean
当Spring容器中没有io.r2dbc.spi.ConnectionFactory
类型的bean时,才会自动注入,这个本人并没有深入研究,一般的项目应该是没有注入该类型的bean。
EnableConfigurationProperties
自动注入@ConfigurationProperties
注解标注的类,将配置的url、username等注入到DataSourceProperties中。
我们在平常使用@ConfigurationProperties
注解注入属性时,仅使用该单个注解时其实是并没有生效的,必须搭配@Component
等注解才会生效(之前虽然都在使用,我也是才注意到ヾ(≧▽≦*)o)。
Import
向Spring容器注入DataSourcePoolMetadataProvidersConfiguration
bean,该步骤暂时不涉及主流程,就不详细分析了。
DataSourceAutoConfiguration类中的DataSourceConfiguration
DataSourceAutoConfiguration
类里面引入了两个关于DataSource
的配置,第一个是内嵌数据源,第二个是池化数据源。
@Conditional
条件注解,只有一个值value,要求是Condition类型的类,Condition有一个方法matches
,返回是否匹配。
其中EmbeddedDatabaseConfiguration类配置的是EmbeddedDatabaseCondition,PooledDataSourceConfiguration配置的是PooledDataSourceCondition。
EmbeddedDatabaseCondition的第一个条件是配置了spring.datasource.url
属性就不满足条件,所以EmbeddedDatabaseConfiguration
不会被注入。
PooledDataSourceCondition继承了AnyNestedCondition类,AnyNestedCondition表示内部类只要任意一个满足条件即可,因为我们配置了spring.datasource.type
属性,所以该条件满足。
@ConditionalOnMissingBean
表示Spring容器中不能有DataSource和XADataSource类型的bean,才会自动注入,这一步保证了我们自动注入的优先级更高,不会出现注入多个DataSource。
因为我们代码中并没有主动注入,所以这一步是满足的,PooledDataSourceConfiguration类上的条件都满足,所以会注入PooledDataSourceConfiguration。
PooledDataSourceConfiguration是一个空类,它是一个空架子,注入它的目的是因为对应的数据源配置。
Spring默认内置了6种类型的数据源,它是根据我们配置的spring.datasource.type
属性来注入的。
DataSourceConfiguration.Hikari解析
@ConditionalOnClass
表示当存在HikariDataSource类时,@ConditionalOnMissingBean
表示当容器中没有DataSource
类型的Bean时,需要注意的是第三个注解@ConditionalOnProperty
,它表示当spring.datasource.type
类型为com.zaxxer.hikari.HikariDataSource
时满足条件,可是我们并没有配置datasourceType,它为什么也生效了呢,因为它的第三个属性matchIfMissing
,如果没有配置,则默认是满足的(我因为这个问题看了好久🤣)。
总结
至此,我们就明白了,当我们只要简单配置一下,springboot就会帮我们自动注入datasource,在这个过程中,springboot大量使用了条件注解,不得不感概,springboot是真的强大。