目录
配置
DruidDataSourceAutoConfigure
new DruidDataSourceWrapper()
DruidDataSource构造函数
init方法
getConnection()
小结
// 配置类
@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();
}
}
// 读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是DruidDataSourceWrapper的父类
实例化DruidDataSource时调用configFromPropety方法
方法主要是从System.getProperties()中读取配置,然后赋值,代码简单不再贴出。
需要注意的是,先执行configFromPropety方法,再执行set方法,配置的先后顺序是配置文件优先,也就是说同时使用System的Properties配置和配置文件配置,最后配置文件的配置会生效。
上边提到,实例化完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();
}
}
上边主要是初始的设置,接下来是获取连接的操作。
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里边
先简单分析到这里,日后遇到问题,再具体问题具体分析