- Druid源码分析(二)-- 初始化DataSource
- 源码目录
- 测试JDBC
- Druid监控页面演示
- debug
druid/src/main/java/com/alibaba/druid/ ├── Constants.java ├── DbType.java ├── DruidRuntimeException.java ├── FastsqlColumnAmbiguousException.java ├── FastsqlException.java ├── TransactionTimeoutException.java ├── VERSION.java ├── filter ├── mock ├── pool ├── proxy ├── sql ├── stat ├── support ├── util └── wall
- filter 自定义扩展插件;
- mock 主要用来mock数据,测试用;
- pool 文件夹最核心,入口是DruidDataSource;
- proxy 代理层;
- sql 负责sql解析的工作;
- stat 监控filter的具体实现;
- support 一些附加功能,比如Json解析等;
- util 通用工具;
- wall 防火墙,防止sql注入;
- 还有个druid-spring-boot-starter,是和spring-boot的集成。
前两天创建了一个测试数据库 druid_test_db
建表
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
执行下面的代码,发现数据库新增了一条数据
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/druid_test_db?allowMultiQueries=true&serverTimezone=GMT%2B8", "root", "");
PreparedStatement stat = con.prepareStatement("insert into user(id, name) values (?, ?)");
stat.setInt(1,1);
stat.setString(2,"bruce");
ResultSet resultSet = stat.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
}
Druid监控页面演示
把 druid-admin 和 druid-spring-boot-starter 、druid-wrapper 模块添加为 maven 项目
然后,查看配置 application.properties,发现测试用例用的是h2
spring.datasource.url=jdbc:h2:file:./demo-db spring.datasource.username=sa spring.datasource.password=sa
运行测试用例,com.alibaba.druid.spring.boot.demo.DemoApplication
访问http://127.0.0.1:8080/druid
可以看到一个监控页面
看运行日志,发现是先自动加载一些默认配置,然后调用DruidDataSource.init()
在 DruidDataSourceAutoConfigure.java 和 DruidDataSource.java的 init 函数打上断点
再次运行测试用例,com.alibaba.druid.spring.boot.demo.DemoApplication
可以看到断点先进的是 DruidDataSourceAutoConfigure.java 的 dataSource()
// DruidDataSourceAutoConfigure.java
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
先加载数据库配置,url、用户名、密码之类的
// DruidDataSource.java
public void configFromPropety(Properties properties) {
String property = properties.getProperty("druid.name");
if (property != null) {
this.setName(property);
}
property = properties.getProperty("druid.url");
if (property != null) {
this.setUrl(property);
}
property = properties.getProperty("druid.username");
if (property != null) {
this.setUsername(property);
}
property = properties.getProperty("druid.password");
if (property != null) {
this.setPassword(property);
}
...
}
如果没有找到前缀是 ‘spring.datasource.druid’ 的配置 ,就会使用前缀是 'spring.datasource’的配置
// DruidDataSourceWrapper.java
public void afterPropertiesSet() throws Exception {
//if not found prefix 'spring.datasource.druid' jdbc properties ,'spring.datasource' prefix jdbc properties will be used.
if (super.getUsername() == null) {
super.setUsername(basicProperties.determineUsername());
}
if (super.getPassword() == null) {
super.setPassword(basicProperties.determinePassword());
}
if (super.getUrl() == null) {
super.setUrl(basicProperties.determineUrl());
}
if (super.getDriverClassName() == null) {
super.setDriverClassName(basicProperties.getDriverClassName());
}
}
然后就是初始化 DruidDataSource
// DruidDataSource.java
public void init() throws SQLException {
if (inited) {
// 第一次校验inited,如果初始化过,直接返回
return;
}
// bug fixed for dead lock, for issue #2980
DruidDriver.getInstance();
// 获取锁
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
boolean init = false;
try {
if (inited) {
// 第二次校验inited,如果初始化过,直接返回
return;
}
...
}
}
这里用了一个 volatile 修饰的字段 inited 和 ReentrantLock 重入锁来保证性能和避免重复初始化
相当于单例模式的双重 synchronized



