druid是一个数据库连接池,它持有一系列的数据库连接,当应用程序需要数据库连接时,通过druid的数据源可以直接获取连接。同时它也有很多附加功能,比如监控等。本文主要分析druid的数据源DruidDataSource的初始化过程。
在应用程序中,我们一般都是直接创建DruidDataSource对象,然后通过它的getConnection()方法获取数据库连接。下面我们主要解析DruidDataSource的构造方法,看看它都做了什么事情。
public DruidDataSource(boolean fairLock) {
super(fairLock);//DruidDataSource会创建一个重入锁,该入参用于设置锁是否是公平锁,默认是非公平锁
configFromPropety(System.getProperties());
}
public void configFromPropety(Properties properties) {
{
//设置druid的名字
String property = properties.getProperty("druid.name");
if (property != null) {
this.setName(property);
}
}
//设置数据库连接的url
{
String property = properties.getProperty("druid.url");
if (property != null) {
this.setUrl(property);
}
}
//中间代码进行了省略,该方法主要是将一些数据库连接参数设置到DruidDataSource的属性中,在创建数据库连接时,数据源会将这些参数设置到连接对象中
{
String property = properties.getProperty("druid.initConnectionSqls");
if (property != null && property.length() > 0) {
try {
StringTokenizer tokenizer = new StringTokenizer(property, ";");
setConnectionInitSqls(Collections.list(tokenizer));
} catch (NumberFormatException e) {
LOG.error("illegal property 'druid.initConnectionSqls'", e);
}
}
}
{
String property = System.getProperty("druid.load.spifilter.skip");
if (property != null && !"false".equals(property)) {
loadSpifilterSkip = true;
}
}
{
String property = System.getProperty("druid.checkExecuteTime");
if (property != null && !"false".equals(property)) {
checkExecuteTime = true;
}
}
}
从上面的代码可以看到,DruidDataSource的构造方法还是比较简单的,主要是用于初始化属性值。
下面我们在分析一下该类的getConnection()方法。
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
init();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
getConnection()方法一共做了两件事:1)初始化;2)获取数据库连接,如果配置了过滤器,则在连接池前面增加过滤器(过滤器在以后的文章详细介绍)。
首先我们看下初始化方法init():
public void init() throws SQLException {
if (inited) {
return;//防止重复初始化
}
DruidDriver.getInstance();//创建DruidDriver对象
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());
//作为初始化下面四个属性的种子,启动的时候从1开始
this.id = DruidDriver.createDataSourceId();
//当创建多个数据源的时候,id才会出现大于1的情况,如果只有一个数据源对象,id为1
if (this.id > 1) {
long delta = (this.id - 1) * 100000;
//作为数据库连接的一个标示,打印日志时使用
this.connectionIdSeedUpdater.addAndGet(this, delta);
//在过滤器中使用,作为Statement代理对象的一个标示,打印日志时使用
this.statementIdSeedUpdater.addAndGet(this, delta);
//在过滤器中使用,作为ResultSet代理对象的一个标示
this.resultSetIdSeedUpdater.addAndGet(this, delta);
//作为TransactionInfo对象的一个标示
this.transactionIdSeedUpdater.addAndGet(this, delta);
}
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
//根据jdbcUrl中的信息初始化数据库连接url、过滤器
//这要求jdbcUrl必须以jdbc:wrap-jdbc:开头
initFromWrapDriverUrl();
//根据jdbcUrl初始化socketTimeOut和connectTimeOut,目前只能对mysql使用这种方式初始化
initFromUrlOrProperties();
}
if (connectTimeout == 0) {
socketTimeout = DEFAULT_TIME_CONNECT_TIMEOUT_MILLIS;
}
if (socketTimeout == 0) {
socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS;
}
//初始化过滤器
for (Filter filter : filters) {
filter.init(this);
}
//从url中判断出数据库类型,比如是db2还是mysql
if (this.dbTypeName == null || this.dbTypeName.length() == 0) {
this.dbTypeName = JdbcUtils.getDbType(jdbcUrl, null);
}
DbType dbType = DbType.of(this.dbTypeName);
if (JdbcUtils.isMysqlDbType(dbType)) {
boolean cacheServerConfigurationSet = false;
if (this.connectProperties.containsKey("cacheServerConfiguration")) {
cacheServerConfigurationSet = true;
} else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {
cacheServerConfigurationSet = true;
}
if (cacheServerConfigurationSet) {
this.connectProperties.put("cacheServerConfiguration", "true");
}
}
//对属性值进行校验
if (maxActive <= 0) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (maxActive < minIdle) {
throw new IllegalArgumentException("illegal maxActive " + maxActive);
}
if (getInitialSize() > maxActive) {
throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
}
if (timeBetweenLogStatsMillis > 0 && useGlobalDataSourceStat) {
throw new IllegalArgumentException("timeBetweenLogStatsMillis not support useGlobalDataSourceStat=true");
}
if (maxEvictableIdleTimeMillis < minEvictableIdleTimeMillis) {
throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
}
if (keepAlive && keepAliveBetweenTimeMillis <= timeBetweenEvictionRunsMillis) {
throw new SQLException("keepAliveBetweenTimeMillis must be grater than timeBetweenEvictionRunsMillis");
}
if (this.driverClass != null) {
this.driverClass = driverClass.trim();
}
//加载配置了@AutoLoad的过滤器,不过可以通过设置loadSpifilterSkip跳过该方法
initFromSPIServiceLoader();
//获得DriverClass类的全限定类名
resolveDriver();
//对db2和oracle的validationQuery属性值做一下检查,检查语句是否符合语法
initCheck();
//当调用Connection对象的setNetworkTimeout方法时,通过netTimeoutExecutor执行回调处理
this.netTimeoutExecutor = new SynchronousExecutor();
//创建ExceptionSorter对象,
//ExceptionSorter的作用是当数据库出现异常时,通过它判断是否是致命异常,如果是,数据源可能会废弃当前连接,重新创建
initExceptionSorter();
//初始化ValidConnectionChecker对象,该对象可以在创建连接或者使用连接前做一些检查,
//比如执行validationQuery语句,或者调用mysql的ConnectionImpl.pingInternal()
initValidConnectionChecker();
//检查一些参数,如果参数设置有问题,会打印日志
validationQueryCheck();
//创建监控对象JdbcDataSourceStat
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);
//创建存储连接对象的数组
connections = new DruidConnectionHolder[maxActive];
evictConnections = new DruidConnectionHolder[maxActive];
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();
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();
}
}
//创建打印日志的线程,每过timeBetweenLogStatsMillis毫秒后打印一次数据源日志
//该日志会把一些重要的参数信息打印出来,并且还会打印出存活线程数
//如果timeBetweenLogStatsMillis的值为小于等于0,则不会创建该线程
createAndLogThread();
//如果createScheduler为null,则创建CreateConnectionThread后台线程
//该线程的作用为创建连接对象
createAndStartCreatorThread();
//创建一个后台线程,每过一段时间检查是否有不可用的线程,如果有则剔除
createAndStartDestroyThread();
initedLatch.await();
init = true;
initedTime = new Date();
registerMbean();
if (connectError != null && poolingCount == 0) {
throw connectError;
}
//keepAlive默认为false,如果设置为true,则会创建minIdle个连接
if (keepAlive) {
// async fill to minIdle
if (createScheduler != null) {
for (int i = 0; i < minIdle; ++i) {
submitCreateTask(true);
}
} else {
this.emptySignal();
}
}
} catch (Error e) {
LOG.error("{dataSource-" + this.getID() + "} init error", e);
throw e;
} finally {
inited = true;
lock.unlock();//解锁
}
}
可以看到init()方法主要是做参数的初始化,然后就创建连接对象,执行完init()方法后,如果设置了过滤器,则先执行过滤器,否则调用getConnectionDirect()方法,该方法用于返回连接对象。