1.1 SpringAutoConfig
对于一个SpringBoot Starter 我们都会从他的 spring.factories
开始看起,因为这里定义了其配置类信息,我们找到如下配置类 DruidDataSourceAutoConfigure
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
我们看一下他只初始化了一个 bean dataSource
:
@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);
// 自定义了初始化方法 init 所以,bean初始化会调用 DataSource 的init 方法。
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
这里还有很多通过 @Import 注解调注入的 Config 类,我们先看主流程,如何初始化 dataSource
。我们可以先看一下 DruidDataSourceWrapper
的类继承信息:
可以看到它是继承与 DruidDataSource 的,而 DruidDataSource 实现了 DataSource 接口。
1.2 初始化 DataSource
接着就来到了我们 DruidDataSourceWrapper
这个 Bean 的初始化流程,我们看上面的 DruidDataSourceAutoConfigure
定义了 bean 的初始化方法 init
。所以我们可以直接追溯到 DruidDataSource 的 init 方法。方法有点长,我们将主要的逻辑放出来:
public void init() throws SQLException {
// 获取 DruidDriver 实例
DruidDriver.getInstance();
// 获取锁,防止并发初始化
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
boolean init = false;
try {
// 锁双重检查
if (inited) {
return;
}
// 获取调用栈信息
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());
// 通过原子类获取信息
this.id = DruidDriver.createDataSourceId();
// 检查是否第一次初始化
.......
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
// 检查 JDBC URL 是否以 jdbc:wrap-jdbc 开头,假如是就需要启动代理
initFromWrapDriverUrl();
}
// 遍历所有 filter 调用 init 方法,我们这里配置了 statfilter
for (Filter filter : filters) {
filter.init(this);
}
// 获取数据库类型
if (this.dbTypeName == null || this.dbTypeName.length() == 0) {
this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null);
}
// 获取 DB 类型
....
// 配置检查
...
// 获取驱动类信息
if (this.driverClass != null) {
this.driverClass = driverClass.trim();
}
// 通过 SPI 方式来初始化 com.alibaba.druid.filter.Filter
initFromSPIServiceLoader();
// 创建 mysql 驱动
resolveDriver();
initCheck();
// 根据不同的数据库来初始化通过的 错误处理器 MySqlExceptionSorter
initExceptionSorter();
initValidConnectionChecker();
validationQueryCheck();
......
dataSourceStat.setResetStatEnable(this.resetStatEnable);
// 初始化 holder
connections = new DruidConnectionHolder[maxActive];
// 设置回收的connection holder
evictConnections = new DruidConnectionHolder[maxActive];
// 设置 keepAlive 的 connection holder
keepAliveConnections = new DruidConnectionHolder[maxActive];
SQLException connectError = null;
// 检查是否需要异步初始化
if (createScheduler != null && asyncInit) {
for (int i = 0; i < initialSize; ++i) {
submitCreateTask(true);
}
} else if (!asyncInit) {
// init connections
while (poolingCount < initialSize) {
try {
// 创建物理链接
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
// 创建 holder
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount++] = holder;
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
if (initExceptionThrow) {
connectError = ex;
break;
} else {
Thread.sleep(3000);
}
}
}
if (poolingCount > 0) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
}
createAndLogThread();
// 创建创建者线程 这里 initedLatch countDown 了一次。
createAndStartCreatorThread();
// 创建销毁线程
createAndStartDestroyThread();
// 等待创建好 connection
initedLatch.await();
init = true;
initedTime = new Date();
// 注册 mbean
registerMbean();
} catch (SQLException e) {
.......
} finally {
inited = true;
// 解锁
lock.unlock();
......
}
}
上面代码基本都有注释,下面来终结一下这里的整个流程:
- 检查是否已经初始化,假如没有,使用锁锁住,防止并非创建。
- 通过 SPI 的方式加载所有的 Filter,并调用其 init 方法。
- 对配置信息进行检查,看是否有不合法的。
- 生成 Mysql 的驱动和错误处理器。
- 初始化三个 connect holder 的数组,分别是
connections
evictConnections
keepAliveConnections
具体含义后面解析。 - 创建创建链接的线程和销毁链接的线程,并等待创建链接线程启动。
- 注册 Mbean 最后解锁。
网友评论