1. 程式人生 > >Hikaricp原始碼解讀(4)——Proxy*代理類介紹

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();
}