歡迎光臨
每天分享高質量文章

【追光者系列】HikariCP原始碼分析之位元組碼修改類庫Javassist委託實現動態代理

摘自【工匠小豬豬的技術世界】 

  1. 這是一個系列,有興趣的朋友可以持續關註

  2. 如果你有HikariCP使用上的問題,可以給我留言,我們一起溝通討論

  3. 希望大家可以提供我一些案例,我也希望可以支援你們做一些調優


概述

很多人都會問HikariCP為什麼那麼快?之前的兩篇文章【追光者系列】HikariCP原始碼分析之FastList 和 【追光者系列】HikariCP原始碼分析之ConcurrentBag 是第一第二彈,本文就是第三彈。

在Down-the-Rabbit-Hole中,作者提到了We’re in your bytecodez的位元組碼最佳化

In order to make HikariCP as fast as it is, we went down to bytecode-level engineering, and beyond. We pulled out every trick we know to help the JIT help you. We studied the bytecode output of the compiler, and even the assembly output of the JIT to limit key routines to less than the JIT inline-threshold. We flattened inheritance hierarchies, shadowed member variables, eliminated casts.

位元組碼最佳化這塊作者還提到了 this change removed a static field access, a push and pop from the stack, and made the invocation easier for the JIT to optimize because the callsite is guaranteed not to change.感興趣的可以看一下 https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole。

作者提升效能的秘方是:大量的效能收益來自於代理的最佳化,如包裝Connection,Statement等。那麼本文就來帶讀者揭開這神秘的面紗。

原始碼綱要

hikariCP主要對java.sql.*提供了五個代理類

  • ProxyConnection(proxy class for java.sql.Connection)

  • ProxyStatement(proxy class for java.sql.Statement)

  • ProxyPreparedStatement(proxy class for java.sql.PreparedStatement)

  • ProxyCallableStatement(proxy class for java.sql.CallableStatement)

  • ProxyResultSet(proxy class for java.sql.ResultSet)

緊密結合以上五個代理類的還有兩個類ProxyFactory(A factory class that produces proxies around instances of the standard JDBC interfaces)和JavassistProxyFactory(This class generates the proxy objects for {@link Connection}, {@link Statement},{@link PreparedStatement}, and {@link CallableStatement}.Additionally it injects method bodies into the {@link ProxyFactory} class methods that can instantiate instances of the generated proxies.)。

我們看一下ProxyFactory這個工廠類,大家是不是可以看到對上面的五個代理類提供的方法只有一行直接拋異常IllegalStateException的程式碼,並且提示你You need to run the CLI build and you need target/classes in your classpath to run

註釋寫著“Body is replaced (injected) by JavassistProxyFactory”,其實方法body中的程式碼是在編譯時呼叫JavassistProxyFactory才生成的。

  1. package com.zaxxer.hikari.pool;

  2. import java.sql.CallableStatement;

  3. import java.sql.Connection;

  4. import java.sql.PreparedStatement;

  5. import java.sql.ResultSet;

  6. import java.sql.Statement;

  7. import com.zaxxer.hikari.util.FastList;

  8. /**

  9. * A factory class that produces proxies around instances of the standard

  10. * JDBC interfaces.

  11. *

  12. * @author Brett Wooldridge

  13. */

  14. @SuppressWarnings("unused")

  15. public final class ProxyFactory {

  16.   private ProxyFactory() {

  17.      // unconstructable

  18.   }

  19.   /**

  20.    * Create a proxy for the specified {@link Connection} instance.

  21.    * @param poolEntry the PoolEntry holding pool state

  22.    * @param connection the raw database Connection

  23.    * @param openStatements a reusable list to track open Statement instances

  24.    * @param leakTask the ProxyLeakTask for this connection

  25.    * @param now the current timestamp

  26.    * @param isReadOnly the default readOnly state of the connection

  27.    * @param isAutoCommit the default autoCommit state of the connection

  28.    * @return a proxy that wraps the specified {@link Connection}

  29.    */

  30.   static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) {

  31.      // Body is replaced (injected) by JavassistProxyFactory

  32.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  33.   }

  34.   static Statement getProxyStatement(final ProxyConnection connection, final Statement statement) {

  35.      // Body is replaced (injected) by JavassistProxyFactory

  36.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  37.   }

  38.   static CallableStatement getProxyCallableStatement(final ProxyConnection connection, final CallableStatement statement) {

  39.      // Body is replaced (injected) by JavassistProxyFactory

  40.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  41.   }

  42.   static PreparedStatement getProxyPreparedStatement(final ProxyConnection connection, final PreparedStatement statement) {

  43.      // Body is replaced (injected) by JavassistProxyFactory

  44.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  45.   }

  46.   static ResultSet getProxyResultSet(final ProxyConnection connection, final ProxyStatement statement, final ResultSet resultSet) {

  47.      // Body is replaced (injected) by JavassistProxyFactory

  48.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  49.   }

  50. }

JavassistProxyFactory

JavassistProxyFactory存在於工具包裡com.zaxxer.hikari.util裡,之所以使用Javassist生成動態代理,是因為其速度更快,相比於JDK Proxy生成的位元組碼更少,精簡了很多不必要的位元組碼。

javassist

javassist是一個位元組碼類庫,可以用他來動態生成類,動態修改類等等,還有一個比較常見的用途是AOP,比如對一些類統一加許可權過濾,加日誌監控等等。

Javassist 不僅是一個處理位元組碼的庫,還有一項優點:可以用 Javassist 改變 Java 類的位元組碼,而無需真正瞭解關於位元組碼或者 Java 虛擬機器(Java virtual machine JVM)結構的任何內容。比起在單條指令水平上工作的框架,它確實使位元組碼操作更可具有可行性了。

Javassist 使您可以檢查、編輯以及建立 Java 二進位制類。檢查方面基本上與透過 Reflection API 直接在 Java 中進行的一樣,但是當想要修改類而不只是執行它們時,則另一種訪問這些資訊的方法就很有用了。這是因為 JVM 設計上並沒有提供在類裝載到 JVM 中後訪問原始類資料的任何方法,這項工作需要在 JVM 之外完成。

Javassist 使用 javassist.ClassPool 類跟蹤和控制所操作的類。這個類的工作方式與 JVM 類裝載器非常相似,但是有一個重要的區別是它不是將裝載的、要執行的類作為應用程式的一部分連結,類池使所裝載的類可以透過 Javassist API 作為資料使用。可以使用預設的類池,它是從 JVM 搜尋路徑中裝載的,也可以定義一個搜尋您自己的路徑串列的類池。甚至可以直接從位元組陣列或者流中裝載二進位制類,以及從頭開始建立新類。

裝載到類池中的類由 javassist.CtClass 實體表示。與標準的 Java java.lang.Class 類一樣, CtClass 提供了檢查類資料(如欄位和方法)的方法。不過,這隻是 CtClass 的部分內容,它還定義了在類中新增新欄位、方法和建構式、以及改變類、父類和介面的方法。奇怪的是,Javassist 沒有提供刪除一個類中欄位、方法或者建構式的任何方法。

欄位、方法和建構式分別由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的實體表示。這些類定義了修改由它們所表示的物件的所有方法的方法,包括方法或者建構式中的實際位元組碼內容。

這篇來自阿裡的文章做了一個動態代理的效能對比(http://javatar.iteye.com/blog/814426),得出的結論如下:

  1. ASM和JAVAASSIST位元組碼生成方式不相上下,都很快,是CGLIB的5倍。

  2. CGLIB次之,是JDK自帶的兩倍。

  3. JDK自帶的再次之,因JDK1.6對動態代理做了最佳化,如果用低版本JDK更慢,要註意的是JDK也是透過位元組碼生成來實現動態代理的,而不是反射。

  4. JAVAASSIST提供者動態代理介面最慢,比JDK自帶的還慢。  (這也是為什麼網上有人說JAVAASSIST比JDK還慢的原因,用JAVAASSIST最好別用它提供的動態代理介面,而可以考慮用它的位元組碼生成方式)

差異的原因是各方案生成的位元組碼不一樣,像JDK和CGLIB都考慮了很多因素,以及繼承或包裝了自己的一些類,所以生成的位元組碼非常大,而我們很多時候用不上這些,而手工生成的位元組碼非常小,所以速度快。

最終該阿裡團隊決定使用JAVAASSIST的位元組碼生成代理方式,雖然ASM稍快,但並沒有快一個數量級,而JAVAASSIST的位元組碼生成方式比ASM方便,JAVAASSIST只需用字串拼接出Java原始碼,便可生成相應位元組碼,而ASM需要手工寫位元組碼。

該測試可能還是有些問題的。其實效能的根本原因還是在於反射。JdkHandler中的 return method.invoke(delegate, objects); 是影響效能的關鍵。如果JAVAASSIST Bytecode Proxy生成的代理類,也是透過JdkHanlder去實現的話,效能就和JDK自身的動態代理沒什麼區別了。 javassit採用的是直接呼叫,而cglib走了methodProxy.invoke(),說白了還是反射呼叫。如果實施cglib的直接呼叫,比如使用的Dispatcher或則LazyLoader。最後的生成的位元組就是一個直接呼叫,效能上就可以和javassist持平。

但是綜上所述,javassist相比於JDK Proxy生成的位元組碼更少,精簡了很多不必要的位元組碼。透過最佳化並精簡位元組碼,提升了hikariCP的效能。

原始碼解析

我們看一下JavassistProxyFactory的原始碼

  1. /**

  2. * This class generates the proxy objects for {@link Connection}, {@link Statement},

  3. * {@link PreparedStatement}, and {@link CallableStatement}.  Additionally it injects

  4. * method bodies into the {@link ProxyFactory} class methods that can instantiate

  5. * instances of the generated proxies.

  6. *

  7. * @author Brett Wooldridge

  8. */

  9. public final class JavassistProxyFactory {

  10.   private static ClassPool classPool;

  11.   private static String genDirectory = "";

  12.   public static void main(String... args) {

  13.      classPool = new ClassPool();

  14.      classPool.importPackage("java.sql");

  15.      classPool.appendClassPath(new LoaderClassPath(JavassistProxyFactory.class.getClassLoader()));

  16.      if (args.length > 0) {

  17.         genDirectory = args[0];

  18.      }

  19.      try {

  20.         // Cast is not needed for these

  21.         String methodBody = "{ try { return delegate.method($); } catch (SQLException e) { throw checkException(e); } }";

  22.         generateProxyClass(Connection.class, ProxyConnection.class.getName(), methodBody);

  23.         generateProxyClass(Statement.class, ProxyStatement.class.getName(), methodBody);

  24.         generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody);

  25.         // For these we have to cast the delegate

  26.         methodBody = "{ try { return ((cast) delegate).method($); } catch (SQLException e) { throw checkException(e); } }";

  27.         generateProxyClass(PreparedStatement.class, ProxyPreparedStatement.class.getName(), methodBody);

  28.         generateProxyClass(CallableStatement.class, ProxyCallableStatement.class.getName(), methodBody);

  29.         modifyProxyFactory();

  30.      }

  31.      catch (Exception e) {

  32.         throw new RuntimeException(e);

  33.      }

  34.   }

  35.   private static void modifyProxyFactory() throws Exception {

  36.      System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory");

  37.      String packageName = ProxyConnection.class.getPackage().getName();

  38.      CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory");

  39.      for (CtMethod method : proxyCt.getMethods()) {

  40.         switch (method.getName()) {

  41.         case "getProxyConnection":

  42.            method.setBody("{return new " + packageName + ".HikariProxyConnection($);}");

  43.            break;

  44.         case "getProxyStatement":

  45.            method.setBody("{return new " + packageName + ".HikariProxyStatement($);}");

  46.            break;

  47.         case "getProxyPreparedStatement":

  48.            method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($);}");

  49.            break;

  50.         case "getProxyCallableStatement":

  51.            method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($);}");

  52.            break;

  53.         case "getProxyResultSet":

  54.            method.setBody("{return new " + packageName + ".HikariProxyResultSet($);}");

  55.            break;

  56.         default:

  57.            // unhandled method

  58.            break;

  59.         }

  60.      }

  61.      proxyCt.writeFile(genDirectory + "target/classes");

  62.   }

  63.   /**

  64.    *  Generate Javassist Proxy Classes

  65.    */

  66.   private static <T> void generateProxyClass(Class<T> primaryInterface, String superClassName, String methodBody) throws Exception {

  67.      String newClassName = superClassName.replaceAll("(.+)\\.(\\w+)", "$1.Hikari$2");

  68.      CtClass superCt = classPool.getCtClass(superClassName);

  69.      CtClass targetCt = classPool.makeClass(newClassName, superCt);

  70.      targetCt.setModifiers(Modifier.FINAL);

  71.      System.out.println("Generating " + newClassName);

  72.      targetCt.setModifiers(Modifier.PUBLIC);

  73.      // Make a set of method signatures we inherit implementation for, so we don't generate delegates for these

  74.      Set<String> superSigs = new HashSet<>();

  75.      for (CtMethod method : superCt.getMethods()) {

  76.         if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {

  77.            superSigs.add(method.getName() + method.getSignature());

  78.         }

  79.      }

  80.      Set<String> methods = new HashSet<>();

  81.      Set<Class>> interfaces = getAllInterfaces(primaryInterface);

  82.      for (Class> intf : interfaces) {

  83.         CtClass intfCt = classPool.getCtClass(intf.getName());

  84.         targetCt.addInterface(intfCt);

  85.         for (CtMethod intfMethod : intfCt.getDeclaredMethods()) {

  86.            final String signature = intfMethod.getName() + intfMethod.getSignature();

  87.            // don't generate delegates for methods we override

  88.            if (superSigs.contains(signature)) {

  89.               continue;

  90.            }

  91.            // Ignore already added methods that come from other interfaces

  92.            if (methods.contains(signature)) {

  93.               continue;

  94.            }

  95.            // Track what methods we've added

  96.            methods.add(signature);

  97.            // Clone the method we want to inject into

  98.            CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);

  99.            String modifiedBody = methodBody;

  100.            // If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call

  101.            CtMethod superMethod = superCt.getMethod(intfMethod.getName(), intfMethod.getSignature());

  102.            if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT && !isDefaultMethod(intf, intfCt, intfMethod)) {

  103.               modifiedBody = modifiedBody.replace("((cast) ", "");

  104.               modifiedBody = modifiedBody.replace("delegate", "super");

  105.               modifiedBody = modifiedBody.replace("super)", "super");

  106.            }

  107.            modifiedBody = modifiedBody.replace("cast", primaryInterface.getName());

  108.            // Generate a method that simply invokes the same method on the delegate

  109.            if (isThrowsSqlException(intfMethod)) {

  110.               modifiedBody = modifiedBody.replace("method", method.getName());

  111.            }

  112.            else {

  113.               modifiedBody = "{ return ((cast) delegate).method($); }".replace("method", method.getName()).replace("cast", primaryInterface.getName());

  114.            }

  115.            if (method.getReturnType() == CtClass.voidType) {

  116.               modifiedBody = modifiedBody.replace("return", "");

  117.            }

  118.            method.setBody(modifiedBody);

  119.            targetCt.addMethod(method);

  120.         }

  121.      }

  122.      targetCt.getClassFile().setMajorVersion(ClassFile.JAVA_8);

  123.      targetCt.writeFile(genDirectory + "target/classes");

  124.   }

  125.   private static boolean isThrowsSqlException(CtMethod method) {

  126.      try {

  127.         for (CtClass clazz : method.getExceptionTypes()) {

  128.            if (clazz.getSimpleName().equals("SQLException")) {

  129.               return true;

  130.            }

  131.         }

  132.      }

  133.      catch (NotFoundException e) {

  134.         // fall thru

  135.      }

  136.      return false;

  137.   }

  138.   private static boolean isDefaultMethod(Class> intf, CtClass intfCt, CtMethod intfMethod) throws Exception {

  139.      List<Class>> paramTypes = new ArrayList<>();

  140.      for (CtClass pt : intfMethod.getParameterTypes()) {

  141.         paramTypes.add(toJavaClass(pt));

  142.      }

  143.      return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[paramTypes.size()])).toString().contains("default ");

  144.   }

  145.   private static Set<Class>> getAllInterfaces(Class> clazz)

  146.   {

  147.      Set<Class>> interfaces = new HashSet<>();

  148.      for (Class> intf : Arrays.asList(clazz.getInterfaces())) {

  149.         if (intf.getInterfaces().length > 0) {

  150.            interfaces.addAll(getAllInterfaces(intf));

  151.         }

  152.         interfaces.add(intf);

  153.      }

  154.      if (clazz.getSuperclass() != null) {

  155.         interfaces.addAll(getAllInterfaces(clazz.getSuperclass()));

  156.      }

  157.      if (clazz.isInterface()) {

  158.         interfaces.add(clazz);

  159.      }

  160.      return interfaces;

  161.   }

  162.   private static Class> toJavaClass(CtClass cls) throws Exception

  163.   {

  164.      if (cls.getName().endsWith("[]")) {

  165.         return Array.newInstance(toJavaClass(cls.getName().replace("[]", "")), 0).getClass();

  166.      }

  167.      else {

  168.         return toJavaClass(cls.getName());

  169.      }

  170.   }

  171.   private static Class> toJavaClass(String cn) throws Exception

  172.   {

  173.      switch (cn) {

  174.      case "int":

  175.         return int.class;

  176.      case "long":

  177.         return long.class;

  178.      case "short":

  179.         return short.class;

  180.      case "byte":

  181.         return byte.class;

  182.      case "float":

  183.         return float.class;

  184.      case "double":

  185.         return double.class;

  186.      case "boolean":

  187.         return boolean.class;

  188.      case "char":

  189.         return char.class;

  190.      case "void":

  191.         return void.class;

  192.      default:

  193.         return Class.forName(cn);

  194.      }

  195.   }

  196. }

generateProxyClass負責生成實際使用的代理類位元組碼,modifyProxyFactory對應修改工廠類中的代理類獲取方法。

  1. private static void modifyProxyFactory() throws Exception {

  2.      System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory");

  3.      String packageName = ProxyConnection.class.getPackage().getName();

  4.      CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.pool.ProxyFactory");

  5.      for (CtMethod method : proxyCt.getMethods()) {

  6.         switch (method.getName()) {

  7.         case "getProxyConnection":

  8.            method.setBody("{return new " + packageName + ".HikariProxyConnection($);}");

  9.            break;

  10.         case "getProxyStatement":

  11.            method.setBody("{return new " + packageName + ".HikariProxyStatement($);}");

  12.            break;

  13.         case "getProxyPreparedStatement":

  14.            method.setBody("{return new " + packageName + ".HikariProxyPreparedStatement($);}");

  15.            break;

  16.         case "getProxyCallableStatement":

  17.            method.setBody("{return new " + packageName + ".HikariProxyCallableStatement($);}");

  18.            break;

  19.         case "getProxyResultSet":

  20.            method.setBody("{return new " + packageName + ".HikariProxyResultSet($);}");

  21.            break;

  22.         default:

  23.            // unhandled method

  24.            break;

  25.         }

  26.      }

  27.      proxyCt.writeFile(genDirectory + "target/classes");

  28.   }

generateProxyClass核心程式碼如下:

  1.  /**

  2.    *  Generate Javassist Proxy Classes

  3.    */

  4.   private static <T> void generateProxyClass(Class<T> primaryInterface, String superClassName, String methodBody) throws Exception

  5.   {

  6.      String newClassName = superClassName.replaceAll("(.+)\\.(\\w+)", "$1.Hikari$2");

  7.      CtClass superCt = classPool.getCtClass(superClassName);

  8.      CtClass targetCt = classPool.makeClass(newClassName, superCt);

  9.      targetCt.setModifiers(Modifier.FINAL);

  10.      System.out.println("Generating " + newClassName);

  11.      targetCt.setModifiers(Modifier.PUBLIC);

  12.      // Make a set of method signatures we inherit implementation for, so we don't generate delegates for these

  13.      Set<String> superSigs = new HashSet<>();

  14.      for (CtMethod method : superCt.getMethods()) {

  15.         if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {

  16.            superSigs.add(method.getName() + method.getSignature());

  17.         }

  18.      }

  19.      Set<String> methods = new HashSet<>();

  20.      Set<Class>> interfaces = getAllInterfaces(primaryInterface);

  21.      for (Class> intf : interfaces) {

  22.         CtClass intfCt = classPool.getCtClass(intf.getName());

  23.         targetCt.addInterface(intfCt);

  24.         for (CtMethod intfMethod : intfCt.getDeclaredMethods()) {

  25.            final String signature = intfMethod.getName() + intfMethod.getSignature();

  26.            // don't generate delegates for methods we override

  27.            if (superSigs.contains(signature)) {

  28.               continue;

  29.            }

  30.            // Ignore already added methods that come from other interfaces

  31.            if (methods.contains(signature)) {

  32.               continue;

  33.            }

  34.            // Track what methods we've added

  35.            methods.add(signature);

  36.            // Clone the method we want to inject into

  37.            CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);

  38.            String modifiedBody = methodBody;

  39.            // If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call

  40.            CtMethod superMethod = superCt.getMethod(intfMethod.getName(), intfMethod.getSignature());

  41.            if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT && !isDefaultMethod(intf, intfCt, intfMethod)) {

  42.               modifiedBody = modifiedBody.replace("((cast) ", "");

  43.               modifiedBody = modifiedBody.replace("delegate", "super");

  44.               modifiedBody = modifiedBody.replace("super)", "super");

  45.            }

  46.            modifiedBody = modifiedBody.replace("cast", primaryInterface.getName());

  47.            // Generate a method that simply invokes the same method on the delegate

  48.            if (isThrowsSqlException(intfMethod)) {

  49.               modifiedBody = modifiedBody.replace("method", method.getName());

  50.            }

  51.            else {

  52.               modifiedBody = "{ return ((cast) delegate).method($); }".replace("method", method.getName()).replace("cast", primaryInterface.getName());

  53.            }

  54.            if (method.getReturnType() == CtClass.voidType) {

  55.               modifiedBody = modifiedBody.replace("return", "");

  56.            }

  57.            method.setBody(modifiedBody);

  58.            targetCt.addMethod(method);

  59.         }

  60.      }

  61.      targetCt.getClassFile().setMajorVersion(ClassFile.JAVA_8);

  62.      targetCt.writeFile(genDirectory + "target/classes");

  63.   }

以 generateProxyClass(ResultSet.class, ProxyResultSet.class.getName(), methodBody); 來看

我看的是3.1.1-SNAPSHOT版本的原始碼,這段程式碼可以看到採用java8並放到了target/classes目錄下。

透過繼承ProxyResultSet來生成HikariProxyResultSet,methodBody中的method替換成對應方法的方法名。

這裡展示一下生成的HikariProxyResultSet的部分程式碼:

  1. public class HikariProxyResultSet extends ProxyResultSet implements ResultSet, AutoCloseable, Wrapper {

  2.    public boolean next() throws SQLException {

  3.        try {

  4.            return super.delegate.next();

  5.        } catch (SQLException var2) {

  6.            throw this.checkException(var2);

  7.        }

  8.    }

  9.    public void close() throws SQLException {

  10.        try {

  11.            super.delegate.close();

  12.        } catch (SQLException var2) {

  13.            throw this.checkException(var2);

  14.        }

  15.    }

  16.    public boolean wasNull() throws SQLException {

  17.        try {

  18.            return super.delegate.wasNull();

  19.        } catch (SQLException var2) {

  20.            throw this.checkException(var2);

  21.        }

  22.    }

  23.    public String getString(int var1) throws SQLException {

  24.        try {

  25.            return super.delegate.getString(var1);

  26.        } catch (SQLException var3) {

  27.            throw this.checkException(var3);

  28.        }

  29.    }

ProxyResultSet

該代理類主要為updateRow、insertRow、deleteRow增加了執行記錄connection.markCommitStateDirty()

原始碼如下:

  1. /**

  2. * This is the proxy class for java.sql.ResultSet.

  3. *

  4. * @author Brett Wooldridge

  5. */

  6. public abstract class ProxyResultSet implements ResultSet {

  7.   protected final ProxyConnection connection;

  8.   protected final ProxyStatement statement;

  9.   final ResultSet delegate;

  10.   protected ProxyResultSet(ProxyConnection connection, ProxyStatement statement, ResultSet resultSet) {

  11.      this.connection = connection;

  12.      this.statement = statement;

  13.      this.delegate = resultSet;

  14.   }

  15.   @SuppressWarnings("unused")

  16.   final SQLException checkException(SQLException e) {

  17.      return connection.checkException(e);

  18.   }

  19.   /** {@inheritDoc} */

  20.   @Override

  21.   public String toString() {

  22.      return this.getClass().getSimpleName() + '@' + System.identityHashCode(this) + " wrapping " + delegate;

  23.   }

  24.   // **********************************************************************

  25.   //                 Overridden java.sql.ResultSet Methods

  26.   // **********************************************************************

  27.   /** {@inheritDoc} */

  28.   @Override

  29.   public final Statement getStatement() throws SQLException {

  30.      return statement;

  31.   }

  32.   /** {@inheritDoc} */

  33.   @Override

  34.   public void updateRow() throws SQLException {

  35.      connection.markCommitStateDirty();

  36.      delegate.updateRow();

  37.   }

  38.   /** {@inheritDoc} */

  39.   @Override

  40.   public void insertRow() throws SQLException {

  41.      connection.markCommitStateDirty();

  42.      delegate.insertRow();

  43.   }

  44.   /** {@inheritDoc} */

  45.   @Override

  46.   public void deleteRow() throws SQLException {

  47.      connection.markCommitStateDirty();

  48.      delegate.deleteRow();

  49.   }

  50.   /** {@inheritDoc} */

  51.   @Override

  52.   @SuppressWarnings("unchecked")

  53.   public final <T> T unwrap(Class<T> iface) throws SQLException {

  54.      if (iface.isInstance(delegate)) {

  55.         return (T) delegate;

  56.      }

  57.      else if (delegate != null) {

  58.          return delegate.unwrap(iface);

  59.      }

  60.      throw new SQLException("Wrapped ResultSet is not an instance of " + iface);

  61.   }

  62. }

ProxyStatement

該類主要implements了java.sql.Statement並實現了其方法。

執行過程中除了傳回ResultSet的需要傳回ProxyFactory.getProxyResultSet(connection, this, resultSet);

  1. static ResultSet getProxyResultSet(final ProxyConnection connection, final ProxyStatement statement, final ResultSet resultSet) {

  2.      // Body is replaced (injected) by JavassistProxyFactory

  3.      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");

  4.   }

就是上文提及的位元組碼,其他直接傳回delegate代理物件。

  1. /** {@inheritDoc} */

  2.   @Override

  3.   public boolean execute(String sql) throws SQLException {

  4.      connection.markCommitStateDirty();

  5.      return delegate.execute(sql);

  6.   }

  7.   /** {@inheritDoc} */

  8.   @Override

  9.   public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {

  10.      connection.markCommitStateDirty();

  11.      return delegate.execute(sql, autoGeneratedKeys);

  12.   }

  13.   /** {@inheritDoc} */

  14.   @Override

  15.   public ResultSet executeQuery(String sql) throws SQLException {

  16.      connection.markCommitStateDirty();

  17.      ResultSet resultSet = delegate.executeQuery(sql);

  18.      return ProxyFactory.getProxyResultSet(connection, this, resultSet);

  19.   }

關於其close方法得提一下上一節的 【追光者系列】HikariCP原始碼分析之FastList

  1. // **********************************************************************

  2.   //                 Overridden java.sql.Statement Methods

  3.   // **********************************************************************

  4.   /** {@inheritDoc} */

  5.   @Override

  6.   public final void close() throws SQLException

  7.   {

  8.   // 放置重覆關閉

  9. synchronized (this) {

  10. if (isClosed) {

  11. return;

  12. }

  13. isClosed = true;

  14. }

  15. // 移出快取

  16. connection.untrackStatement(delegate);

  17. try {

  18. // 關閉代理

  19. delegate.close();

  20. }

  21. catch (SQLException e) {

  22. throw connection.checkException(e);

  23. }

  24.   }

connection.untrackStatement(delegate); 這裡我們可以看到原始碼如下

  1. final synchronized void untrackStatement(final Statement statement) {

  2.      openStatements.remove(statement);

  3.   }

  4.   private final FastList<Statement> openStatements;

  5.     protected ProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit) {

  6.      this.poolEntry = poolEntry;

  7.      this.delegate = connection;

  8.      this.openStatements = openStatements;

  9.      this.leakTask = leakTask;

  10.      this.lastAccess = now;

  11.      this.isReadOnly = isReadOnly;

  12.      this.isAutoCommit = isAutoCommit;

  13.   }

FastList是一個List介面的精簡實現,只實現了介面中必要的幾個方法。JDK ArrayList每次呼叫get()方法時都會進行rangeCheck檢查索引是否越界,FastList的實現中去除了這一檢查,只要保證索引合法那麼rangeCheck就成為了不必要的計算開銷(當然開銷極小)。此外,HikariCP使用List來儲存開啟的Statement,當Statement關閉或Connection關閉時需要將對應的Statement從List中移除。通常情況下,同一個Connection建立了多個Statement時,後開啟的Statement會先關閉。ArrayList的remove(Object)方法是從頭開始遍歷陣列,而FastList是從陣列的尾部開始遍歷,因此更為高效。

簡而言之就是 自定義陣列型別(FastList)代替ArrayList:避免每次get()呼叫都要進行range check,避免呼叫remove()時的從頭到尾的掃描

ProxyConnection

該類主要實現了java.sql.Connection的一系列方法,凡涉及Statement、CallableStatement、PreparedStatement的方法都用到了先快取statement,然後透過ProxyFactory工廠生成的位元組碼代理類

  1. /** {@inheritDoc} */

  2.   @Override

  3.   public CallableStatement prepareCall(String sql, int resultSetType, int concurrency, int holdability) throws SQLException {

  4.      return ProxyFactory.getProxyCallableStatement(this, trackStatement(delegate.prepareCall(sql, resultSetType, concurrency, holdability)));

  5.   }

  6.   /** {@inheritDoc} */

  7.   @Override

  8.   public PreparedStatement prepareStatement(String sql) throws SQLException {

  9.      return ProxyFactory.getProxyPreparedStatement(this, trackStatement(delegate.prepareStatement(sql)));

  10.   }

其關閉程式碼需要註意一點的是,因為closeStatements裡會evict poolEntry,所以放到判斷外。

  1. // **********************************************************************

  2.   //              "Overridden" java.sql.Connection Methods

  3.   // **********************************************************************

  4.   /** {@inheritDoc} */

  5.   @Override

  6.   public final void close() throws SQLException {

  7.      // Closing statements can cause connection eviction, so this must run before the conditional below

  8.      closeStatements();

  9.      if (delegate != ClosedConnection.CLOSED_CONNECTION) {

  10.         leakTask.cancel();

  11.         try {

  12.            if (isCommitStateDirty && !isAutoCommit) {

  13.               delegate.rollback();

  14.               lastAccess = currentTime();

  15.               LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", poolEntry.getPoolName(), delegate);

  16.            }

  17.            if (dirtyBits != 0) {

  18.               poolEntry.resetConnectionState(this, dirtyBits);

  19.               lastAccess = currentTime();

  20.            }

  21.            delegate.clearWarnings();

  22.         }

  23.         catch (SQLException e) {

  24.            // when connections are aborted, exceptions are often thrown that should not reach the application

  25.            if (!poolEntry.isMarkedEvicted()) {

  26.               throw checkException(e);

  27.            }

  28.         }

  29.         finally {

  30.            delegate = ClosedConnection.CLOSED_CONNECTION;

  31.            poolEntry.recycle(lastAccess);

  32.         }

  33.      }

  34.   }

ProxyConnection如下圖,可以看到該類是真正使用FastList來進行最佳化的 private final FastList openStatements;

  1. // 用於標識連線被訪問或存在可提交資料

  2. final void markCommitStateDirty() {

  3.   if (isAutoCommit) {

  4.      lastAccess = currentTime();

  5.   }

  6.   else {

  7.      isCommitStateDirty = true;

  8.   }

  9. }

  10. // 快取statement

  11. private synchronized <T extends Statement> T trackStatement(final T statement) {

  12.   openStatements.add(statement);

  13.   return statement;

  14. }

  15. // 移出statement快取

  16. final synchronized void untrackStatement(final Statement statement) {

  17.   openStatements.remove(statement);

  18. }

  19. // 關閉全部已開啟的statement(只在close方法中呼叫)

  20. @SuppressWarnings("EmptyTryBlock")

  21. private synchronized void closeStatements() {

  22.   final int size = openStatements.size();

  23.   if (size > 0) {

  24.      for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {

  25.         try (Statement ignored = openStatements.get(i)) {

  26.            // automatic resource cleanup

  27.         }

  28.         catch (SQLException e) {

  29.            LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",

  30.                        poolEntry.getPoolName(), delegate);

  31.            leakTask.cancel();

  32.            poolEntry.evict("(exception closing Statements during Connection.close())");

  33.            delegate = ClosedConnection.CLOSED_CONNECTION;

  34.         }

  35.      }

  36.      openStatements.clear();

  37.   }

  38. }

In order to generate proxies for Connection, Statement, and ResultSet instances HikariCP was initially using a singleton factory, held in the case of ConnectionProxy in a static field (PROXY_FACTORY).

ClosedConnection是ProxyConnection中動態代理實現的唯一實體化物件。全域性唯一變數,作為已關閉連線的代理取用,為連線關閉後外界代理連線的取用呼叫提供處理,同時唯一類減少了記憶體消耗和比對代價。

  1. // **********************************************************************

  2.   //                         Private classes

  3.   // **********************************************************************

  4.   private static final class ClosedConnection

  5.   {

  6.      static final Connection CLOSED_CONNECTION = getClosedConnection();

  7.      private static Connection getClosedConnection()

  8.      {

  9.         InvocationHandler handler = (proxy, method, args) -> {

  10.          // 只保留3個方法的快速傳回,其他均丟擲異常

  11.            final String methodName = method.getName();

  12.            if ("abort".equals(methodName)) {

  13.               return Void.TYPE;

  14.            }

  15.            else if ("isValid".equals(methodName)) {

  16.               return Boolean.FALSE;

  17.            }

  18.            else if ("toString".equals(methodName)) {

  19.               return ClosedConnection.class.getCanonicalName();

  20.            }

  21.            throw new SQLException("Connection is closed");

  22.         };

  23.         return (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), new Class[] { Connection.class }, handler);

  24.      }

  25.   }

這裡InvocationHandler還是使用到了反射,之前提過的,多多少少對效能也會有點損失,一次全域性唯一變數一次反射,個人認為,這裡反射如果能最佳化為直接呼叫位元組碼,或許效能還能再上一個臺階。

參考資料

  • http://yonglin4605.iteye.com/blog/1396494

  • http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/

  • http://zhxing.iteye.com/blog/1703305

  • http://www.cnblogs.com/taisenki/p/7716724.html

END

贊(0)

分享創造快樂