[druid 源码解析] 2 初始化连接池

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 的类继承信息:

image.png

可以看到它是继承与 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();
        ......
        }
    }

上面代码基本都有注释,下面来终结一下这里的整个流程:

  1. 检查是否已经初始化,假如没有,使用锁锁住,防止并非创建。
  2. 通过 SPI 的方式加载所有的 Filter,并调用其 init 方法。
  3. 对配置信息进行检查,看是否有不合法的。
  4. 生成 Mysql 的驱动和错误处理器。
  5. 初始化三个 connect holder 的数组,分别是 connections evictConnections keepAliveConnections 具体含义后面解析。
  6. 创建创建链接的线程和销毁链接的线程,并等待创建链接线程启动。
  7. 注册 Mbean 最后解锁。

你可能感兴趣的:([druid 源码解析] 2 初始化连接池)