Hikaricp原始碼解讀(4)——Proxy*代理類介紹
4、Proxy*代理類介紹
本文以v2.7.2原始碼為主進行分析
HikariCP不同於一般連線池如proxool、c3p0等使用動態代理實現連線的操作轉移,而是通過Javassist結合部分自定義程式碼實現對應介面實現的方式,減少了代理連線建立的代價,以下是HikariCP中的主要代理相關類:
- ProxyConnection.java
- ProxyStatement.java
- ProxyPreparedStatement.java
- ProxyCallableStatement.java
- ProxyResultSet.java
- ProxyFactory.java(工廠類)
- JavassistProxyFactory.java(程式碼重構)
以上程式碼通過Javassist進行程式碼重構建之後生成實際使用的對應介面代理類:
- HikariProxyConnection.java
- HikariProxyStatement.java
- HikariProxyPreparedStatement.java
- HikariProxyCallableStatement.java
- HikariProxyResultSet.java
我們從JavassistProxyFactory.java
入手,其核心程式碼如下:
try {
// Cast is not needed for these
String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }" ;
generateProxyClass(Connection.class, ProxyConnection.class.getName(), methodBody);
generateProxyClass(Statement.class, ProxyStatement.class.getName(), methodBody);
generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody);
// For these we have to cast the delegate
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }" ;
generateProxyClass(PreparedStatement.class, ProxyPreparedStatement.class.getName(), methodBody);
generateProxyClass(CallableStatement.class, ProxyCallableStatement.class.getName(), methodBody);
modifyProxyFactory();
}
catch (Exception e) {
throw new RuntimeException(e);
}
由上可以看出,核心構造方法為
- generateProxyClass
- modifyProxyFactory
其中generateProxyClass
負責生成實際使用的代理類位元組碼,modifyProxyFactory
對應修改工廠類中的代理類獲取方法。
generateProxyClass
Proxy*.java中定義的方法,在繼承類中不進行overwrite,其他方法使用delegate執行對應的方法(methodBody中的method替換成對應方法的方法名):
// 區分是否丟擲SQLException異常
if (isThrowsSqlException(intfMethod)) {
modifiedBody = modifiedBody.replace("method", method.getName());
}
else {
modifiedBody = "{ return ((cast) delegate).method($$); }".replace("method", method.getName()).replace("cast", primaryInterface.getName());
}
modifyProxyFactory
直接替換Proxy*.java為HikariProxy*.java
switch (method.getName()) {
case "getProxyConnection":
method.setBody("{return new " + packageName + ".HikariProxyConnection($$);}");
break;
case "getProxyStatement":
method.setBody("{return new " + packageName + ".HikariProxyStatement($$);}");
break;
case "getProxyPreparedStatement":
method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($$);}");
break;
case "getProxyCallableStatement":
method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($$);}");
break;
case "getProxyResultSet":
method.setBody("{return new " + packageName + ".HikariProxyResultSet($$);}");
break;
default:
// unhandled method
break;
}
由此可知,Proxy*.java中的實現是代理類的核心程式碼實現。以下通過ProxyConnection.java、ProxyStatement.java、ProxyResultSet.java三個類展開介紹:
ProxyConnection.java
本類中程式碼大致分為三部分:以close為關鍵的程式碼overwrite、獨立實現的ClosedConnection(動態代理實現的唯一例項化物件)、Hikari 連線池的自定義函式邏輯。
- 自定義函式
主要實現對開啟的statement的快取管理和連線標識:
// 用於標識連線被訪問或存在可提交資料
final void markCommitStateDirty()
{
if (isAutoCommit) {
lastAccess = currentTime();
}
else {
isCommitStateDirty = true;
}
}
// 快取statement
private synchronized <T extends Statement> T trackStatement(final T statement)
{
openStatements.add(statement);
return statement;
}
// 移出statement快取
final synchronized void untrackStatement(final Statement statement)
{
openStatements.remove(statement);
}
// 關閉全部已開啟的statement(只在close方法中呼叫)
@SuppressWarnings("EmptyTryBlock")
private synchronized void closeStatements()
{
final int size = openStatements.size();
if (size > 0) {
for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
try (Statement ignored = openStatements.get(i)) {
// automatic resource cleanup
}
catch (SQLException e) {
LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
poolEntry.getPoolName(), delegate);
leakTask.cancel();
poolEntry.evict("(exception closing Statements during Connection.close())");
delegate = ClosedConnection.CLOSED_CONNECTION;
}
}
openStatements.clear();
}
}
- ClosedConnection
java動態代理生成的全域性唯一變數,作為已關閉連線的代理引用,為連線關閉後外界代理連線的引用呼叫提供處理,同時唯一類減少了記憶體消耗和比對代價。程式碼如下:
private static final class ClosedConnection
{
static final Connection CLOSED_CONNECTION = getClosedConnection();
private static Connection getClosedConnection()
{
InvocationHandler handler = (proxy, method, args) -> {
// 只保留3個方法的快速返回,其他均丟擲異常。
final String methodName = method.getName();
if ("abort".equals(methodName)) {
return Void.TYPE;
}
else if ("isValid".equals(methodName)) {
return Boolean.FALSE;
}
else if ("toString".equals(methodName)) {
return ClosedConnection.class.getCanonicalName();
}
throw new SQLException("Connection is closed");
};
return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);
}
}
- overwrite
主要修改statement獲取、close、狀態修改相關的方法和資料變更:
// ************statement獲取************
public PreparedStatement prepareStatement(String sql) throws SQLException
{
// 先快取statement,然後通過工廠方法生成代理類。
return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));
}
// 其他statement獲取類似
// ***********狀態修改相關***************
public void setTransactionIsolation(int level) throws SQLException
{
// 設定狀態
delegate.setTransactionIsolation(level);
// 記錄變更值
transactionIsolation = level;
// 記錄變更位
dirtyBits |= DIRTY_BIT_ISOLATION;
}
//setCatalog、setReadOnly、setAutoCommit、
//setNetworkTimeout、setSchema五個方法類似
// **************close*****************
public final void close() throws SQLException
{
// 關閉全部開啟的連線(有可能觸發連線關閉,所以放到判斷外)
closeStatements();
if (delegate != ClosedConnection.CLOSED_CONNECTION) {
……
try {
// 如果存在提交資料且不是自動提交,則回滾重置資料
if (isCommitStateDirty && !isAutoCommit) {
delegate.rollback();
lastAccess = currentTime();
LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);
}
// 如果存在設定變更位,則進行重置
if (dirtyBits != 0) {
poolEntry.resetConnectionState(this, dirtyBits);
lastAccess = currentTime();
}
// 重置
delegate.clearWarnings();
}
catch (SQLException e) {
……
}
finally {
// 代理置為關閉連線
delegate = ClosedConnection.CLOSED_CONNECTION;
// 回收PoolEntry(若PoolEntry已經被關閉和remove,則由下個接收執行緒丟棄或者threadlocal中無引用被回收)
poolEntry.recycle(lastAccess);
}
}
}
// ***********commit\rollback***********
public void commit\rollback() throws SQLException
{
// 呼叫方法
delegate.commit\rollback();
isCommitStateDirty = false;
// 更新時間
lastAccess = currentTime();
}
ProxyStatement.java
由overwrite構成,主要包含執行方法、close兩類。
// **************執行方法****************
public ResultSet executeQuery(String sql) throws SQLException
{
// 記錄執行
connection.markCommitStateDirty();
// 呼叫方法
ResultSet resultSet = delegate.executeQuery(sql);
// 生成代理ResultSet
return ProxyFactory.getProxyResultSet(connection, this, resultSet);
}
// **************close****************
public final void close() throws SQLException
{
// 放置重複關閉
synchronized (this) {
if (isClosed) {
return;
}
isClosed = true;
}
// 移出快取
connection.untrackStatement(delegate);
try {
// 關閉代理
delegate.close();
}
catch (SQLException e) {
throw connection.checkException(e);
}
}
ProxyResultSet.java
此類代理最簡單,就只是分別為updateRow、insertRow、deleteRow增加了執行記錄connection.markCommitStateDirty(),示例如下:
public void updateRow() throws SQLException
{
connection.markCommitStateDirty();
delegate.updateRow();
}