dbcp源碼分析

轉載自:https://www.ktanx.com/blog/p/1274
如果自己實現一個DataSource,最基本的數據準備
1.dbs=new DataSource()
2.connPool=new ConnectionPool()關聯到dbs
3.driverConnectionFactorty創建最基本conn,add到connPool
4.dbs中的connPool並不是可池子化管理的。沒有validate,不可用的沒有清理,沒有遵循maxIdle等設置。所以此處需要一個可池子化管理的連接工廠,根據maxActive,MaxIdle,testOnIdle等參數來對池子中的鏈接進行管理,所以出現了poolConnFactory

上面是自己整理的,下面我們看源碼具體說明下:

/**
* Create (if necessary) and return a connection to the database.
*
* @throws SQLException if a database access error occurs
* @return a database connection
*/
public Connection getConnection() throws SQLException {
return createDataSource().getConnection();
}
從源代碼可以看出,獲取連接之前先要創建數據源,創建的數據源將作爲BasicDataSource內部的數據源,爲獲取連接提供服務。下面對這個createDataSource()方法進行解釋,先看它的源代碼:

/**
*

Create (if necessary) and return the internal data source we are
* using to manage our connections.


*
*

IMPLEMENTATION NOTE - It is tempting to use the
* “double checked locking” idiom in an attempt to avoid synchronizing
* on every single call to this method. However, this idiom fails to
* work correctly in the face of some optimizations that are legal for
* a JVM to perform.


*
* @throws SQLException if the object pool cannot be created.
*/
protected synchronized DataSource createDataSource()
throws SQLException {
if (closed) {
throw new SQLException(“Data source is closed”);
}
// Return the pool if we have already created it
if (dataSource != null) {
return (dataSource);
}
// create factory which returns raw physical connections
ConnectionFactory driverConnectionFactory = createConnectionFactory();
// create a pool for our connections
createConnectionPool();
// Set up statement pool, if desired
GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
-1, // unlimited maxActive (per key)
GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
0, // maxWait
1, // maxIdle (per key)
maxOpenPreparedStatements);
}
// Set up the poolable connection factory
createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);
// Create and return the pooling data source to manage the connections
createDataSourceInstance();
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException(“Error preloading the connection pool”, e);
}
return dataSource;
}
從源代碼可以看出,createDataSource()方法通過7步,逐步構造出一個數據源,下面是詳細的步驟:

1、檢查數據源是否關閉或者是否創建完成,如果關閉了就拋異常,如果已經創建完成就直接返回。

2、調用createConnectionFactory()創建JDBC連接工廠driverConnectionFactory,這個工廠使用數據庫驅動來創建最底層的JDBC連接

3、調用createConnectionPool()創建數據源使用的連接池,連接池顧名思義就是緩存JDBC連接的地方。

4、如果需要就設置statement的緩存池,這個一般不需要設置

5、調用createPoolableConnectionFactory創建PoolableConnection**(可池子化管理的連接工廠)**的工廠,這個工廠使用上述driverConnectionFactory來創建底層JDBC連接,然後包裝出一個PoolableConnection,這個PoolableConnection與連接池設置了一對多的關係,也就是說,連接池中存在多個PoolableConnection,每個PoolableConnection都關聯同一個連接池,這樣的好處是便於該表PoolableConnection的close方法的行爲,具體會在後面詳細分析。

6、調用createDataSourceInstance()創建內部數據源

7、爲連接池中添加PoolableConnection

經過以上7步,一個數據源就形成了,這裏明確一點,一個數據源本質就是連接池+連接+管理策略。下面,將對每一步做詳細的分析。

2.1 JDBC連接工廠driverConnectionFactory的創建過程

   先上源代碼:

/**
* Creates a JDBC connection factory for this datasource. This method only
* exists so subclasses can replace the implementation class.
*/
protected ConnectionFactory createConnectionFactory() throws SQLException {
// Load the JDBC driver class
Class driverFromCCL = null;
if (driverClassName != null) {
try {
try {
if (driverClassLoader == null) {
Class.forName(driverClassName);
} else {
Class.forName(driverClassName, true, driverClassLoader);
}
} catch (ClassNotFoundException cnfe) {
driverFromCCL = Thread.currentThread(
).getContextClassLoader().loadClass(
driverClassName);
}
} catch (Throwable t) {
String message = “Cannot load JDBC driver class '” +
driverClassName + “’”;
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
}
// Create a JDBC driver instance
Driver driver = null;
try {
if (driverFromCCL == null) {
driver = DriverManager.getDriver(url);
} else {
// Usage of DriverManager is not possible, as it does not
// respect the ContextClassLoader
driver = (Driver) driverFromCCL.newInstance();
if (!driver.acceptsURL(url)) {
throw new SQLException(“No suitable driver”, “08001”);
}
}
} catch (Throwable t) {
String message = “Cannot create JDBC driver of class '” +
(driverClassName != null ? driverClassName : “”) +
“’ for connect URL '” + url + “’”;
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
// Can’t test without a validationQuery
if (validationQuery == null) {
setTestOnBorrow(false);
setTestOnReturn(false);
setTestWhileIdle(false);
}
// Set up the driver connection factory we will use
String user = username;
if (user != null) {
connectionProperties.put(“user”, user);
} else {
log(“DBCP DataSource configured without a ‘username’”);
}
String pwd = password;
if (pwd != null) {
connectionProperties.put(“password”, pwd);
} else {
log(“DBCP DataSource configured without a ‘password’”);
}
ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, url, connectionProperties);
return driverConnectionFactory;
}
上面一連串代碼幹了什麼呢?其實就幹了兩件事:1、獲取數據庫驅動 2、使用驅動以及參數(url、username、password)構造一個工廠。一旦這個工廠構建完畢了,就可以來生成連接,而這個連接的生成其實是驅動加上配置來完成的,下面是這個過程的時序圖
在這裏插入圖片描述

獲取驅動的時候,使用了兩種方法:一種是Class.forName,一種是DriverManager.getDriver(url),獲取到驅動後再獲取用戶名密碼以及URL這些參數,最後創建DriverConnectionFactory,而DriverConnectionFactory生成連接的方式可以從源代碼看出:

public DriverConnectionFactory(Driver driver, String connectUri, Properties props) {
_driver = driver;
_connectUri = connectUri;
_props = props;
}
public Connection createConnection() throws SQLException {
return _driver.connect(_connectUri,_props);
}
就是用驅動和配置參數獲取。

2.2 創建連接池的過程

 先看源代碼:

/**
* Creates a connection pool for this datasource. This method only exists
* so subclasses can replace the implementation class.
*/
protected void createConnectionPool() {
// Create an object pool to contain our active connections
GenericObjectPool gop;
if ((abandonedConfig != null) && (abandonedConfig.getRemoveAbandoned())) {
gop = new AbandonedObjectPool(null,abandonedConfig);
}
else {
gop = new GenericObjectPool();
}
gop.setMaxActive(maxActive);
gop.setMaxIdle(maxIdle);
gop.setMinIdle(minIdle);
gop.setMaxWait(maxWait);
gop.setTestOnBorrow(testOnBorrow);
gop.setTestOnReturn(testOnReturn);
gop.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
gop.setTestWhileIdle(testWhileIdle);
connectionPool = gop;
}
在創建連接池的時候,用到了common-pool裏的GenericObjectPool,今天不解讀common-pool,所以不做深入分析,我們只需知道,對於JDBC連接的緩存以及管理其實是交給GenericObjectPool的,DBCP其實只是負責創建這樣一種pool然後使用它而已。

2.3 創建statement緩存池

 一般來說,statement並不是重量級的對象,創建過程消耗的資源並不像JDBC連接那樣重,所以沒必要做緩存池化,這裏爲了簡便起見,對此不做分析。

2.4 創建PoolableConnectionFactory

 這一步是一個承上啓下的過程,承上在於利用上面兩部創建的連接工廠和連接池,構建PoolableConnectionFactory,啓下則在於爲後面的向連接池裏添加連接做準備。

下面先上一張靜態的類關係圖:

在這裏插入圖片描述
然後是源代碼:

/**
* Creates the PoolableConnectionFactory and attaches it to the connection pool. This method only exists
* so subclasses can replace the default implementation.
*
* @param driverConnectionFactory JDBC connection factory
* @param statementPoolFactory statement pool factory (null if statement pooling is turned off)
* @param configuration abandoned connection tracking configuration (null if no tracking)
* @throws SQLException if an error occurs creating the PoolableConnectionFactory
*/
protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory,
KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration) throws SQLException {
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory =
new PoolableConnectionFactory(driverConnectionFactory,
connectionPool,
statementPoolFactory,
validationQuery,
validationQueryTimeout,
connectionInitSqls,
defaultReadOnly,
defaultAutoCommit,
defaultTransactionIsolation,
defaultCatalog,
configuration);
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException(“Cannot create PoolableConnectionFactory (” + e.getMessage() + “)”, e);
}
}
可以看見,在創建PoolableConnectionFactory的時候,需要用到前面創建的driverConnectionFactory以及連接池connectionPool,那麼那個構造函數到底幹了先什麼呢?

public PoolableConnectionFactory(
ConnectionFactory connFactory,
ObjectPool pool,
KeyedObjectPoolFactory stmtPoolFactory,
String validationQuery,
int validationQueryTimeout,
Collection connectionInitSqls,
Boolean defaultReadOnly,
boolean defaultAutoCommit,
int defaultTransactionIsolation,
String defaultCatalog,
AbandonedConfig config) {
_connFactory = connFactory;
_pool = pool;
_config = config;
_pool.setFactory(this);
_stmtPoolFactory = stmtPoolFactory;
_validationQuery = validationQuery;
_validationQueryTimeout = validationQueryTimeout;
_connectionInitSqls = connectionInitSqls;
_defaultReadOnly = defaultReadOnly;
_defaultAutoCommit = defaultAutoCommit;
_defaultTransactionIsolation = defaultTransactionIsolation;
_defaultCatalog = defaultCatalog;
}
它在內部保存了真正的JDBC 連接的工廠以及連接池,然後,通過一句_pool.setFactory(this); 將它自己設置給了連接池。這行代碼十分重要,要理解這行代碼,首先需要明白common-pool中的GenericObjectPool添加內部元素的一般方法,沒錯,那就是必須要傳入一個工廠Factory。GenericObjectPool添加內部元素時會調用addObject()這個方法,內部其實是調用工廠的makeObejct()方法來創建元素,然後再加入到自己的池中。_pool.setFactory(this)這句代碼其實起到了啓下的作用,沒有它,後面的爲連接池添加連接也就不可能完成。

當創建完工廠後,會有個validateConnectionFactory(connectionFactory);這個方法的作用僅僅是用來驗證數據庫連接可使用,看代碼:

protected static void validateConnectionFactory(PoolableConnectionFactory connectionFactory) throws Exception {
Connection conn = null;
try {
conn = (Connection) connectionFactory.makeObject();
connectionFactory.activateObject(conn);
connectionFactory.validateConnection(conn);
connectionFactory.passivateObject(conn);
}
finally {
connectionFactory.destroyObject(conn);
}
}
先是用makeObject方法來創建一個連接,然後做相關驗證(就是用一些初始化sql來試着執行一下,看看能不能連接到數據庫),然後銷燬連接,這裏並沒有向連接池添加連接,真正的添加連接在後面,不過,我們可以先通過下面一張時序圖來看看makeObject方法到底做了什麼。

下面是一張整體流程的時序圖:

在這裏插入圖片描述

 從圖中可以看出,makeObject方法的大致流程:從driverConnectionFactory那裏拿到底層連接,初始化驗證,然後創建PoolableConnection,在創建這個PoolableConnection的時候,將PoolableConnection與連接池關聯了起來,真正做到了連接池和連接之間的一對多的關係,這也爲改變PoolableConnection的close方法提供了方便。

下面是makeObject方法的源代碼:

public Object makeObject() throws Exception {
Connection conn = _connFactory.createConnection();
if (conn == null) {
throw new IllegalStateException(“Connection factory returned null from createConnection”);
}
initializeConnection(conn); //初始化,這個過程可有可無
if(null != _stmtPoolFactory) {
KeyedObjectPool stmtpool = _stmtPoolFactory.createPool();
conn = new PoolingConnection(conn,stmtpool);
stmtpool.setFactory((PoolingConnection)conn);
} //這裏是關鍵
return new PoolableConnection(conn,_pool,_config);
}
其中PoolableConnection的構造函數如下:

/**
*
* @param conn my underlying connection
* @param pool the pool to which I should return when closed
* @param config the abandoned configuration settings
*/
public PoolableConnection(Connection conn, ObjectPool pool, AbandonedConfig config) {
super(conn, config);
_pool = pool;
}
內部關聯了一個連接池,這個連接池的作用體現在PoolableConnection的close方法中:

/**
* Returns me to my pool.
*/
public synchronized void close() throws SQLException {
if (_closed) {
// already closed
return;
}
boolean isUnderlyingConectionClosed;
try {
isUnderlyingConectionClosed = _conn.isClosed();
} catch (SQLException e) {
try {
_pool.invalidateObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException ise) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING the original exception will be rethrown
}
throw (SQLException) new SQLException(“Cannot close connection (isClosed check failed)”).initCause(e);
}
if (!isUnderlyingConectionClosed) {
// Normal close: underlying connection is still open, so we
// simply need to return this proxy to the pool
try {
_pool.returnObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw (SQLException) new SQLException(“Cannot close connection (return to pool failed)”).initCause(e);
}
} else {
// Abnormal close: underlying connection closed unexpectedly, so we
// must destroy this proxy
try {
_pool.invalidateObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING, “Already closed” exception thrown below
}
throw new SQLException(“Already closed.”);
}
}
一行_pool.returnObject(this)表明並非真的關閉了,而是返還給了連接池。

到這裏, PoolableConnectionFactory創建好了,它使用driverConnectionFactory來創建底層連接,通過makeObject來創建PoolableConnection,這個PoolableConnection通過與connectionPool關聯來達到改變close方法的作用,當PoolableConnectionFactory創建好的時候,它自己已經作爲一個工廠類被設置到了connectionPool,後面connectionPool會使用這個工廠來生產PoolableConnection,而生成的所有的PoolableConnection都與connectionPool關聯起來了,可以從connectionPool取出,也可以還給connectionPool。接下來,讓我們來看一看到底怎麼去初始化connectionPool。

2.5 創建數據源並初始化連接池

對應代碼如下:

createDataSourceInstance();
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException(“Error preloading the connection pool”, e);
}
我們先看 createDataSourceInstance();

/**
* Creates the actual data source instance. This method only exists so
* subclasses can replace the implementation class.
*
* @throws SQLException if unable to create a datasource instance
*/
protected void createDataSourceInstance() throws SQLException {
PoolingDataSource pds = new PoolingDataSource(connectionPool);
pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
pds.setLogWriter(logWriter);
dataSource = pds;
}
其實就是創建一個PoolingDataSource,作爲底層真正的數據源,這個PoolingDataSource比較簡單,這裏不做詳細介紹

接下來是一個for循環,通過調用connectionPool.addObject();來爲連接池添加數據庫連接,下面是一張時序圖:
在這裏插入圖片描述

可以看出,在2.4中創建的PoolableConnectionFactory在這裏起作用了,addObject依賴的正是makeObject,而makeObject在上面也介紹過了。

到此爲止,數據源創建好了,連接池裏也有了可以使用的連接,而且每個連接和連接池都做了關聯,改變了close的行爲。這個時候BasicDataSource正是可以工作了,調用getConnection的時候,實際是調用底層數據源的getConnection,而底層數據源其實就是從連接池中獲取的連接。

3、總結

整個數據源最核心的其實就三個東西:一個是連接池,在這裏體現爲common-pool中的GenericObjectPool,它負責緩存和管理連接,所有的配置策略都是由它管理。第二個是連接,這裏的連接就是PoolableConnection,當然它是對底層連接進行了封裝。第三個則是連接池和連接的關係,在此表現爲一對多的互相引用。對數據源的構建則是對連接池,連接以及連接池與連接的關係的構建,掌握了這些點,就基本能掌握數據源的構建。

對於DBCP的構建,個人覺得比較精妙的是通過構建連接和連接池的關聯關係來實現代理的作用,不足之處則是沒有把構建過程抽象出來。其實,完全可以使用建造者模式對BasicDataSource的構建過程進行抽象,從而使構建過程更具有擴展性。

最後,我分析的DBCP是1.4版本,最新的是2.X版本,應該對整個代碼架構做了重大的調整,有機會我再分析。另外,數據源中真正的複雜的部分其實是對連接的緩存和管理,這部分由common-pool解決了,有機會我一定會好好分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章