美文网首页Druiddruid 源码之旅
[druid 源码解析] 2 初始化连接池

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

作者: AndyWei123 | 来源:发表于2021-11-08 23:39 被阅读0次

    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 初始化连接池

        本文链接:https://www.haomeiwen.com/subject/fqnpzltx.html