druid源码浅析

目录

配置

DruidDataSourceAutoConfigure

new DruidDataSourceWrapper()

DruidDataSource构造函数

init方法

 getConnection()

小结


配置

DruidDataSourceAutoConfigure

// 配置类
@Configuration
// 有DruidDataSource类时生效
@ConditionalOnClass(DruidDataSource.class)
// 在DataSourceAutoConfiguration类之前进行配置
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 读取配置
// DruidStatProperties主要是监控网站配置和网站计数过滤器配置
// DataSourceProperties 主要是数据库配置
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
// 导入四个不同的配置类
// DruidSpringAopConfiguration用于启用对 Druid 数据库连接池的 AOP 支持
// DruidStatViewServletConfiguration 根据配置开启Druid 连接池的监控统计功能
// DruidWebStatFilterConfiguration 根据配置开启Web和Druid数据源之间的管理关联监控统计
// DruidFilterConfiguration 根据配置注入各种filter的bean
@Import({DruidSpringAopConfiguration.class,
        DruidStatViewServletConfiguration.class,
        DruidWebStatFilterConfiguration.class,
        DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
    private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
    // 初始化时执行DruidDataSource的init方法
    @Bean(initMethod = "init")
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        LOGGER.info("Init DruidDataSource");
        // 返回DruidDataSourceWrapper实例
        return new DruidDataSourceWrapper();
    }
}

new DruidDataSourceWrapper()

// 读spring.datasource.druid开头的配置
@ConfigurationProperties("spring.datasource.druid")
@Override
    public void afterPropertiesSet() throws Exception {
        //如果不存在spring.datasource.druid的配置,就用'spring.datasource'配置
        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是DruidDataSourceWrapper的父类

实例化DruidDataSource时调用configFromPropety方法

方法主要是从System.getProperties()中读取配置,然后赋值,代码简单不再贴出。

需要注意的是,先执行configFromPropety方法,再执行set方法,配置的先后顺序是配置文件优先,也就是说同时使用System的Properties配置和配置文件配置,最后配置文件的配置会生效。

init方法

上边提到,实例化完dataSource的bean之后会执行DruidDataSource的init方法,让我们看看init方法做了什么

 // 判断是否被初始化过,是则直接退出  
  if (inited) {
      return;
  }
        DruidDriver.getInstance();
        
        final ReentrantLock lock = this.lock;
        try {
            // 尝试获取锁,如果获取失败则等待锁被释放,直到被其他线程打断或者当前线程获取到锁为止
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }
            // 为数据源分配id
            this.id = DruidDriver.createDataSourceId();
            if (this.id > 1) {
                // 更新一些JMX的监控指标
                long delta = (this.id - 1) * 100000;
                this.connectionIdSeedUpdater.addAndGet(this, delta);
                this.statementIdSeedUpdater.addAndGet(this, delta);
                this.resultSetIdSeedUpdater.addAndGet(this, delta);
                this.transactionIdSeedUpdater.addAndGet(this, delta);
            }
            if (this.jdbcUrl != null) {
                this.jdbcUrl = this.jdbcUrl.trim();
                // 以下两个方法是解析url,从中找到配置去配置druidDataSource,包括filter、connectTimeout等
                initFromWrapDriverUrl();
                initFromUrlOrProperties();
            }
// 中间省略一些赋值操作
…… ……
// 从SPI ServiceLoader加载过滤器
initFromSPIServiceLoader()
// 给driver赋值
resolveDriver();
// 对dbType为Oracle、db2、mysql的做出相应处理
initCheck();
            // 根据driverClassName给exceptionSorter赋值,异常分类器可以帮助我们对不同类型的 SQL 异常进行分类和管理,例如识别数据库链接超时、死锁等情况,并进行相应的处理,避免出现严重的数据问题。
            initExceptionSorter();
            // 根据driverClassName给validConnectionChecker赋值,通过合法连接检测器,我们可以过滤掉已关闭或已失效的数据库连接,以避免在数据库操作时出现异常情况。
            initValidConnectionChecker();
            // 在 Druid 数据源内部,默认会使用 Validation Query 来检查每个数据库连接的有效性,以确保连接池中的所有连接都是可用的,从而提高数据库操作的效率和稳定性。
            validationQueryCheck();
         // 看是否配置全局数据源统计,dataSourceStat主要用于收集和记录数据源的性能指标和运行状态信息。通过 JdbcDataSourceStat 类的属性可以了解到数据源运行期间的各种指标信息,
         //并根据这些信息进行优化和调整,以提高数据源的性能和稳定性。同时,我们也可以通过 Druid 的 Web 控制台对数据源进行实时监控和管理,及时发现和解决问题。
         if (isUseGlobalDataSourceStat()) {
                dataSourceStat = JdbcDataSourceStat.getGlobal();
                if (dataSourceStat == null) {
                    dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbTypeName);
                    JdbcDataSourceStat.setGlobal(dataSourceStat);
                }
                if (dataSourceStat.getDbType() == null) {
                    dataSourceStat.setDbType(this.dbTypeName);
                }
            } else {
                dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbTypeName, this.connectProperties);
            }
         dataSourceStat.setResetStatEnable(this.resetStatEnable);
            // 声明了三个类型均为 DruidConnectionHolder 的数组
            // connections 数组表示数据源中所有的有效连接,当客户端请求连接时,连接池会向该数组中获取一个空闲连接,供客户端使用。
            // evictConnections 数组表示要被释放的无效连接,当检查到连接无效时,连接池会将该连接放入该数组中,并在后续时间将这些无效连接关闭和移除,以保证连接池中只有有效可用的连接。
            // keepAliveConnections 数组表示连接池中的存活连接,当连接池对连接进行健康检查时,发现连接处于“存活”状态,则将其放置在该数组中继续使用。
            connections = new DruidConnectionHolder[maxActive];
            evictConnections = new DruidConnectionHolder[maxActive];
            keepAliveConnections = new DruidConnectionHolder[maxActive];
            // 判断创建连接池的调度程序是否为空和asyncInit参数
            // 满足条件则异步创建连接
            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();
                        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();
            // 该线程会监测连接池中的连接数量,如果连接数小于最小活跃连接数,则会异步地创建新的连接并添加到连接池中,以保证连接池中至少存在最小活跃连接数的连接
            createAndStartCreatorThread();
            // 该线程会监测连接池中的空闲连接数量,如果连接数超出了最大空闲连接数,则会异步地销毁多余的连接,以避免连接池中的无效连接过多导致性能下降
            createAndStartDestroyThread();
            // 保证createAndStartCreatorThread完成才继续往下走
            initedLatch.await();
// 用于向 MBean 服务器注册 Druid 数据源的监控信息
// MBean(管理 Bean)是 Java 管理扩展(Java Management Extension,JMX)的核心概念之一,它是一种管理和监测 Java 应用程序的标准化方式。
// 创建一个 MBeanServer 对象,然后调用该对象的 registerMBean() 方法将 Druid 数据源的状态信息和配置信息注册为一个 MBean,即可在 JMX 控制台上查看和管理
registerMbean();
            // 如果设置了keepalive,连接会自动保持活动状态,以减少重新创建连接和验证连接的消耗
            if (keepAlive) {
                // 分为同步创建和异步创建连接
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        submitCreateTask(true);
                    }
                } else {
                    this.emptySignal();
                }
            }

 getConnection()

上边主要是初始的设置,接下来是获取连接的操作。

    public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
        // 生成bean时已经初始化过,不再初始化
        init();
        // 如果过滤器不为空,则获取连接时对连接做一系列处理
        // 如果没使用过滤器则世界获取连接
        if (filters.size() > 0) {
            FilterChainImpl filterChain = new FilterChainImpl(this);
            return filterChain.dataSource_connect(this, maxWaitMillis);
        } else {
            return getConnectionDirect(maxWaitMillis);
        }
    }

 找到getConnectionInternal方法里的

                // 如果设置最大等待时间并且大于0
                if (maxWait > 0) {
                    holder = pollLast(nanos);
                } else {
                    // 如果最大等待时间小于0
                    holder = takeLast();
                }
    DruidConnectionHolder takeLast() throws InterruptedException, SQLException {
        try {    
            while (poolingCount == 0) {
                emptySignal(); // 唤醒init里创建连接线程CreateConnectionThread
                …… ……
                try {
                    notEmpty.await(); // 防止未初始化完成就往下走,初始化一个连接后唤醒这里
                } finally {
                    notEmptyWaitThreadCount--;
                }
                …… ……
            }
        } catch (InterruptedException ie) {
            …… ……
        }
        // poolingCount--
        decrementPoolingCount();
        // 取出链接并把当前位置的connections置空
        DruidConnectionHolder last = connections[poolingCount];
        connections[poolingCount] = null;

        return last;
    }

 当一个连接使用完成之后,会调用DruidPooledConnection的close方法,然后调用DruidDataSource的recycle方法最后调用putLast(holder, currentTimeMillis)方法把连接放回到connections里边

小结

先简单分析到这里,日后遇到问题,再具体问题具体分析

你可能感兴趣的:(源码,java,druid)