SpringBoot default database connection pool HikariCP

Table of contents

 introduction

1. Problem description

2. SpringBoot default database connection pool

3. What is HikariCP?

4. Test dependencies

5. Configuration file

5.1. Database connection parameters

5.2. Basic parameters of connection pool data

5.3. Connection check parameters

5.4. Transaction related parameters

5.5, JMX parameters

6. Analysis of HikariCP source code

6.1, HikariConfig--loading of connection pool configuration

6.2, HikariPool--connection pool

1, HikariPool UML diagram

2、PoolBase

3、HikariPool

4. How to get a link object

6.3, ConcurrentBag--less lock conflicts

7. Why is HikariCP fast?

7.1. Significantly reduce lock competition between threads through code design and optimization

7.2. Introduced more JDK features

7.3. Use javassist to directly modify the class file to generate a dynamic proxy

8. JDK, CGLib, ASM, Javassist performance test

1. Test code

2. Test results


 introduction

        We used a lot of open source database connection pools in the process of developing the project, such as druid, c3p0, BoneCP, etc. The front-end time found a problem when deploying new services. After troubleshooting, we just learned the default database connection pool of SpringBoot Some knowledge of HikariCP. HikariCP official website address:  https://github.com/brettwooldridge/HikariCP

1. Problem description

        After our new project was deployed and launched, we found this warning when observing the logs. After investigation, we found a problem with the DB. The reserved site is as follows.

2. SpringBoot default database connection pool

        Spring-Boot-2.0.0-M1 version changed the default database connection pool from tomcat jdbc pool to HikariCP.

3. What is HikariCP?

        HikariCP is used to create and manage connections, and uses the "pool" method to reuse connections to reduce resource overhead. Like other data sources, it also has functions such as connection number control, connection reliability testing, connection leak control, and cache statements. Like druid, HikariCP also supports monitoring functions.

        HikariCP is currently the fastest connection pool. Even the popular BoneCP stopped maintaining and gave way to it. SpringBoot also set it as the default connection pool.

4. Test dependencies

        Since the official website says that HikariCP is the fastest database connection pool, let's make some attempts to verify the ruthless words released by the official website. Verification is also relatively simple, just add dependencies to the project.

 <!-- JNDI数据源 -->
    <resource-ref>
        <res-ref-name>jdbc/hikariCP-test</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

5. Configuration file

        After adding the dependencies in the above step, let's learn about the various configuration information of HikariCP before the actual operation.

5.1. Database connection parameters

        Note that the url here is spliced ​​with multiple parameters to avoid problems of garbled characters and time zone errors.

#-------------基本属性--------------------------------
jdbcUrl=jdbc:mysql://localhost:3306/github_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true
username=root
password=root
#JDBC驱动使用的Driver实现类类名
#默认为空。会根据jdbcUrl来解析
driverClassName=com.mysql.cj.jdbc.Driver

5.2. Basic parameters of connection pool data

#-------------连接池大小相关参数--------------------------------
#最大连接池数量
#默认为10。可通过JMX动态修改
maximumPoolSize=10

#最小空闲连接数量
#默认与maximumPoolSize一致。可通过JMX动态修改
minimumIdle=0

5.3. Connection check parameters

        Note: For the problem of connection failure, HikariCP compulsorily enables lending test and idle test, does not enable recycling test, and only leak test is optional. All timeouts can be set according to JMX.

#-------------连接检测情况--------------------------------
#用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'
#如果驱动支持JDBC4,建议不设置,因为这时默认会调用Connection.isValid()方法来检测,该方式效率会更高
#默认为空
connectionTestQuery=select 1 from dual

#检测连接是否有效的超时时间,单位毫秒
#最小允许值250 ms
#默认5000 ms。
validationTimeout=5000

#连接保持空闲而不被驱逐的最小时间。单位毫秒。
#该配置只有再minimumIdle < maximumPoolSize才会生效,最小允许值为10000 ms。
#默认值10000*60 = 10分钟。
idleTimeout=600000

#连接对象允许“泄露”的最大时间。单位毫秒
#最小允许值为2000 ms。
#默认0,表示不开启泄露检测。
leakDetectionThreshold=0

#连接最大存活时间。单位毫秒
#最小允许值30000 ms
#默认30分钟。可通过JMX动态修改
maxLifetime=1800000

#获取连接时最大等待时间,单位毫秒
#获取时间超过该配置,将抛出异常。最小允许值250 ms
#默认30000 ms。
connectionTimeout=300000

5.4. Transaction related parameters

#-------------事务相关的属性--------------------------------
#当连接返回池中时是否设置自动提交
#默认为true
autoCommit=true

#当连接从池中取出时是否设置为只读
#默认值false
readOnly=false

#连接池创建的连接的默认的TransactionIsolation状态
#可用值为下列之一:NONE,TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE
#默认值为空,由驱动决定
transactionIsolation=TRANSACTION_REPEATABLE_READ

5.5, JMX parameters

#-------------JMX--------------------------------

#是否允许通过JMX挂起和恢复连接池
#默认为false
allowPoolSuspension=false

#是否开启JMX
#默认false
registerMbeans=true

#数据源名。
#默认自动生成
poolName=

6. Analysis of HikariCP source code

6.1, HikariConfig--loading of connection pool configuration

        In HikariCP, HikariConfig is used to load configuration, and its loading is more concise. Start looking directly from the PropertyElf.setTargetFromProperties(Object, Properties) method.

// 这个方法就是将properties的参数设置到HikariConfig中
public static void setTargetFromProperties(final Object target, final Properties properties)
{
   if (target == null || properties == null) {
      return;
   }

   // 在这里会利用反射获取
   List<Method> methods = Arrays.asList(target.getClass().getMethods());
   // 遍历
   properties.forEach((key, value) -> {
      if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {
         // 如果是dataSource.*的参数,直接加入到dataSourceProperties属性
         ((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);
      }
      else {
         // 如果不是,则通过set方法设置
         setProperty(target, key.toString(), value, methods);
      }
   });
}
private static void setProperty(final Object target, final String propName, final Object propValue, final List<Method> methods)
{
   final Logger logger = LoggerFactory.getLogger(PropertyElf.class);

   // use the english locale to avoid the infamous turkish locale bug
   // 拼接参数的setter方法名 首字母大写
   String methodName = "set" + propName.substring(0, 1).toUpperCase(Locale.ENGLISH) + propName.substring(1);
   // 获取对应的Method 对象
   Method writeMethod = methods.stream().filter(m -> m.getName().equals(methodName) && m.getParameterCount() == 1).findFirst().orElse(null);

   // 如果不存在,按另一套规则拼接参数的setter方法名 全部大写
   if (writeMethod == null) {
      String methodName2 = "set" + propName.toUpperCase(Locale.ENGLISH);
      writeMethod = methods.stream().filter(m -> m.getName().equals(methodName2) && m.getParameterCount() == 1).findFirst().orElse(null);
   }

   // 如果该参数setter方法不存在,则抛出异常,从这里可以看出,HikariCP 中不能存在配错参数名的情况
   if (writeMethod == null) {
      logger.error("Property {} does not exist on target {}", propName, target.getClass());
      throw new RuntimeException(String.format("Property %s does not exist on target %s", propName, target.getClass()));
   }


   // 调用setter方法来配置具体参数。
   try {
      Class<?> paramClass = writeMethod.getParameterTypes()[0];
      if (paramClass == int.class) {
         writeMethod.invoke(target, Integer.parseInt(propValue.toString()));
      }
      else if (paramClass == long.class) {
         writeMethod.invoke(target, Long.parseLong(propValue.toString()));
      }
      else if (paramClass == boolean.class || paramClass == Boolean.class) {
         writeMethod.invoke(target, Boolean.parseBoolean(propValue.toString()));
      }
      else if (paramClass == String.class) {
         writeMethod.invoke(target, propValue.toString());
      }
      else {
         try {
            logger.debug("Try to create a new instance of \"{}\"", propValue.toString());
            writeMethod.invoke(target, Class.forName(propValue.toString()).newInstance());
         }
         catch (InstantiationException | ClassNotFoundException e) {
            logger.debug("Class \"{}\" not found or could not instantiate it (Default constructor)", propValue.toString());
            writeMethod.invoke(target, propValue);
         }
      }
   }
   catch (Exception e) {
      logger.error("Failed to set property {} on target {}", propName, target.getClass(), e);
      throw new RuntimeException(e);
   }
}

6.2, HikariPool--connection pool

        HikariPool is a very important class which is responsible for managing connections.

1, HikariPool UML diagram

HikariPoolMXBean: Use JMX to control the entrance of HikariPool.

/**
 * The javax.management MBean for a Hikari pool instance.
 *
 * @author Brett Wooldridge
 */
public interface HikariPoolMXBean

2、PoolBase

        Configuration information of the HikariPool connection pool.

3、HikariPool

        Connection pool management.

Attributes:

//配置信息。
public final HikariConfig config;
//指标记录器包装类。HikariCP支持Metrics监控
IMetricsTrackerDelegate metricsTracker;
//创建新连接的任务,Callable实现类。一般调用一次创建一个连接
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null /*logging prefix*/);
//创建新连接的任务,Callable实现类。一般调用一次创建一个连接,与前者区别在于它创建最后一个连接,会打印日志
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
private final Collection<Runnable> addConnectionQueueReadOnlyView;
//执行PoolEntryCreator任务的线程池。以addConnectionQueueReadOnlyView作为等待队列
private final ThreadPoolExecutor addConnectionExecutor;
//执行关闭连接的线程池
private final ThreadPoolExecutor closeConnectionExecutor;
//用于执行HouseKeeper(连接检测任务和维持连接池大小)等任务
private final ScheduledExecutorService houseKeepingExecutorService;
//存放连接对象的包。用于borrow、requite、add和remove对象。
private final ConcurrentBag<PoolEntry> connectionBag;

4. How to get a link object

/**
 * Get a connection from the pool, or timeout after the specified number of milliseconds.
 *
 * @param hardTimeout the maximum time to wait for a connection from the pool
 * @return a java.sql.Connection instance
 * @throws SQLException thrown if a timeout occurs trying to obtain a connection
 */
public Connection getConnection(final long hardTimeout) throws SQLException
{
   // 如果我们设置了allowPoolSuspension为true,则这个锁会生效,这个是基于信号量的锁 MAX_PERMITS = 10000,正常情况不会用完,除非你挂起了连接池(通过JMX等方式),10000个permits会被消耗完
   suspendResumeLock.acquire();
   final long startTime = currentTime();

   try {
      // 剩余超时时间
      long timeout = hardTimeout;
      // 循环获取,除非获取到了连接或者超时
      do {
         // 从ConcurrentBag中拿出一个元素
         PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
         // 前面说过,只有超时情况才会返回空,这时会跳出循环并抛出异常
         if (poolEntry == null) {
            break; // We timed out... break and throw exception
         }

         final long now = currentTime();
         // 如果
         // 1、元素被标记为丢弃
         // 2、空闲时间过长
         // 3、连接无效则会丢弃该元素
         // 1&2&3 --> 4、并关闭连接
         if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
            closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
            timeout = hardTimeout - elapsedMillis(startTime);
         }
         else {
            metricsTracker.recordBorrowStats(poolEntry, startTime);
            // 创建Connection代理类,该代理类就是使用Javassist生成的
            return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
         }
      } while (timeout > 0L);

      metricsTracker.recordBorrowTimeoutStats(startTime);

      // 超时抛出异常
      throw createTimeoutException(startTime);
   }
   catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
   }
   finally {
      // 释放一个permit
      suspendResumeLock.release();
   }
}

6.3, ConcurrentBag--less lock conflicts

        In HikariCP, ConcurrentBag is used to store PoolEntry objects (encapsulating Connection objects, IConcurrentBagEntry implementation classes), which can be essentially a resource pool.

 Attributes:

//存放着当前线程返还的PoolEntry对象。如果当前线程再次借用资源,会先从这个列表中获取。注意,这个列表的元素可以被其他线程“偷走”
private final ThreadLocal<List<Object>> threadList;
//添加元素的监听器,由HikariPool实现,在该实现中,如果waiting - addConnectionQueue.size() >= 0,则会让addConnectionExecutor执行PoolEntryCreator任务
private final IBagStateListener listener;
//当前等待获取链接的线程数
private final AtomicInteger waiters;
//元素是否使用弱引用
private final boolean weakThreadLocals;
//这是一个无容量的阻塞队列,每个插入操作需要阻塞等待删除操作,而删除操作不需要等待,如果没有元素插入,会返回null,如果设置了超时时间则需要等待。
private final SynchronousQueue<T> handoffQueue;
//存放着状态为使用中、未使用和保留三种状态的PoolEntry对象。注意,CopyOnWriteArrayList是一个线程安全的集合,在每次写操作时都会采用复制数组的方式来增删元素,读和写使用的是不同的数组,避免了锁竞争
private final CopyOnWriteArrayList<T> sharedList;

method:

        In the following method, the only possible thread switch to is handoffQueue.poll(timeout, NANOSECONDS).

/**
 * The method will borrow a BagEntry from the bag, blocking for the
 * specified timeout if none are available.
 *
 * @param timeout how long to wait before giving up, in units of unit
 * @param timeUnit a <code>TimeUnit</code> determining how to interpret the timeout parameter
 * @return a borrowed instance from the bag or null if a timeout occurs
 * @throws InterruptedException if interrupted while waiting
 */
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
   // 1. 首先从threadList获取对象

   // Try the thread-local list first
   // 获取绑定在当前线程的List<Object>对象,注意这个集合的实现一般为FastList,这是HikariCP自己实现的
   final List<Object> list = threadList.get();
   for (int i = list.size() - 1; i >= 0; i--) {
      // 获取当前元素,并将它从集合中删除
      final Object entry = list.remove(i);
      @SuppressWarnings("unchecked")
      // 如果设置了weakThreadLocals,则存放的是WeakReference对象
      final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
      // 采用CAS方式将获取的对象状态由未使用改为使用中,如果失败说明其他线程正在使用它。
      if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
         return bagEntry;
      }
   }

   // 2.如果还没获取到,会从sharedList中获取对象

   // Otherwise, scan the shared list ... then poll the handoff queue
   // 等待获取连接的线程数+1
   final int waiting = waiters.incrementAndGet();
   try {
      // 遍历sharedList
      for (T bagEntry : sharedList) {
         // 采用CAS方式将获取的对象状态由未使用改为使用中,如果当前元素正在使用,则无法修改成功,进入下一循环
         if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            // If we may have stolen another waiter's connection, request another bag add.
            if (waiting > 1) {
               // 通知监听器添加包元素。如果waiting - addConnectionQueue.size() >= 0,则会让addConnectionExecutor执行PoolEntryCreator任务
               listener.addBagItem(waiting - 1);
            }
            return bagEntry;
         }
      }

      // 通知监听器添加包元素
      listener.addBagItem(waiting);

      // 3.如果还没获取到,会轮训进入handoffQueue队列获取连接对象

      timeout = timeUnit.toNanos(timeout);
      do {
         final long start = currentTime();
         // 从handoffQueue队列中获取并删除元素。这是一个无容量的阻塞队列,插入操作需要阻塞等待删除操作,而删除操作不需要等待,如果没有元素插入,会返回null,如果设置了超时时间则需要等待
         final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
         // 这里会出现三种情况,
         // 1.超时,返回null
         // 2.获取到元素,但状态为正在使用,继续执行
         // 3.获取到元素,元素状态未未使用,修改未使用并返回
         if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            return bagEntry;
         }

         timeout -= elapsedNanos(start);
      } while (timeout > 10_000);

      // 超时返回null
      return null;
   }
   finally {
      // 等待获取连接的线程数-1
      waiters.decrementAndGet();
   }
}

7. Why is HikariCP fast?

7.1. Significantly reduce lock competition between threads through code design and optimization

        1. The introduction of the element state, and the use of the CAS method to modify the state. In ConcurrentBag, use in-use, unused, deleted, and reserved to represent the state of the element, instead of using different collections to maintain elements in different states. The introduction of the concept of element state is critical and provides the basis for the following points. In the method of ConcurrentBag, the CAS method is called in multiple places to judge and modify the element state, and this process does not need to be locked.

        2. The use of threadList. The element returned by the current thread will be bound to ThreadLocal. When the thread acquires the element again, it can be obtained directly on the premise that the element has not been stolen, and there is no need to traverse the sharedList to obtain it;

7.2. Introduced more JDK features

        Especially the tools of the concurrent package. Compared with database connection pools such as DBCP and C3P0, which came out later, it is very convenient to enjoy the convenience brought by the upgrade of JDK.

        1. Use CopyOnWriteArrayList to store elements. In CopyOnWriteArrayList, different arrays are used for reading and writing, which avoids the lock competition between the two. As for writing by multiple threads, a ReentrantLock lock will be added.

        2. Read and write control of sharedList. Borrow and requite are not locked for sharedList, the disadvantage is that it will sacrifice consistency. User threads cannot add elements, only addConnectionExecutor can, and addConnectionExecutor will only open a thread to perform tasks, so there will be no lock competition in the add operation. As for remove is the only method that will cause lock competition, I think it can also be handled with reference to addConnectionExecutor, and the status of PoolEntry is marked as being deleted before adding to the task queue.

7.3. Use javassist to directly modify the class file to generate a dynamic proxy

        1. Use javassist to directly modify the class file to generate a dynamic proxy, which simplifies many unnecessary bytecodes and improves the running speed of the proxy method. Especially after the optimization of JDK1.8, the dynamic agent of JDK, CGlib agent has been an order of magnitude with javassist, asm, etc.

8. JDK, CGLib, ASM, Javassist performance test

        Environment: JDK 1.8, CGLib 3.3.0, ASM JDK's own ASM package, Javassist 3.26.0-GA.

        The data is the result of calling the proxy method 50 million times for three executions.

1. Test code

package cn.zzs.proxy;

import javassist.*;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.FieldVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DecimalFormat;

/**
 * @author lly
 **/
public class App {
    public static void main(String[] args) throws Exception {
        CountService delegate = new CountServiceImpl();
        long time = System.currentTimeMillis();
        CountService jdkProxy = createJdkDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create JDK Proxy: " + time + " ms");

        time = System.currentTimeMillis();
        CountService cglibProxy = createCglibDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create CGLIB Proxy: " + time + " ms");

        time = System.currentTimeMillis();
        CountService javassistProxy = createJavassistDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create JAVAASSIST Proxy: " + time + " ms");

        time = System.currentTimeMillis();
        CountService javassistBytecodeProxy = createJavassistBytecodeDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create JAVAASSIST Bytecode Proxy: " + time + " ms");

        time = System.currentTimeMillis();
        CountService asmBytecodeProxy = createAsmBytecodeDynamicProxy(delegate);
        time = System.currentTimeMillis() - time;
        System.out.println("Create ASM Proxy: " + time + " ms");
        System.out.println("================");

        for (int i = 0; i < 3; i++) {
            test(jdkProxy, "Run JDK Proxy: ");
            test(cglibProxy, "Run CGLIB Proxy: ");
            test(javassistProxy, "Run JAVAASSIST Proxy: ");
            test(javassistBytecodeProxy, "Run JAVAASSIST Bytecode Proxy: ");
            test(asmBytecodeProxy, "Run ASM Bytecode Proxy: ");
            System.out.println("----------------");
        }

    }

    private static void test(CountService service, String label)
            throws Exception {
        service.count(); // warm up
        int count = 50000000;
        long time = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            service.count();
        }
        time = System.currentTimeMillis() - time;
        System.out.println(label + time + " ms, " + new DecimalFormat().format(count / time * 1000) + " t/s");
    }

    private static CountService createJdkDynamicProxy(final CountService delegate) {
        CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
                new Class[]{CountService.class}, new JdkHandler(delegate));
        return jdkProxy;
    }

    private static class JdkHandler implements InvocationHandler {

        final Object delegate;

        JdkHandler(Object delegate) {
            this.delegate = delegate;
        }

        public Object invoke(Object object, Method method, Object[] objects)
                throws Throwable {
            return method.invoke(delegate, objects);
        }
    }

    private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(new CglibInterceptor(delegate));
        enhancer.setInterfaces(new Class[]{CountService.class});
        CountService cglibProxy = (CountService) enhancer.create();
        return cglibProxy;
    }

    private static class CglibInterceptor implements MethodInterceptor {

        final Object delegate;

        CglibInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        public Object intercept(Object object, Method method, Object[] objects,
                                MethodProxy methodProxy) throws Throwable {
            return methodProxy.invoke(delegate, objects);
        }
    }

    private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setInterfaces(new Class[]{CountService.class});
        Class<?> proxyClass = proxyFactory.createClass();
        CountService javassistProxy = (CountService) proxyClass.newInstance();
        ((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
        return javassistProxy;
    }

    private static class JavaAssitInterceptor implements MethodHandler {

        final Object delegate;

        JavaAssitInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        public Object invoke(Object self, Method m, Method proceed,
                             Object[] args) throws Throwable {
            return m.invoke(delegate, args);
        }
    }

    private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
        ClassPool mPool = new ClassPool(true);
        CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
        mCtc.addInterface(mPool.get(CountService.class.getName()));
        mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
        mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
        mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
        Class<?> pc = mCtc.toClass();
        CountService bytecodeProxy = (CountService) pc.newInstance();
        Field filed = bytecodeProxy.getClass().getField("delegate");
        filed.set(bytecodeProxy, delegate);
        return bytecodeProxy;
    }

    private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        String className = CountService.class.getName() + "AsmProxy";
        String classPath = className.replace('.', '/');
        String interfacePath = CountService.class.getName().replace('.', '/');
        classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});

        MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        initVisitor.visitCode();
        initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        initVisitor.visitInsn(Opcodes.RETURN);
        initVisitor.visitMaxs(0, 0);
        initVisitor.visitEnd();

        FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
        fieldVisitor.visitEnd();

        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
        methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
        methodVisitor.visitInsn(Opcodes.IRETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();

        classWriter.visitEnd();
        byte[] code = classWriter.toByteArray();
        CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
        Field filed = bytecodeProxy.getClass().getField("delegate");
        filed.set(bytecodeProxy, delegate);
        return bytecodeProxy;
    }

    private static class ByteArrayClassLoader extends ClassLoader {

        public ByteArrayClassLoader() {
            super(ByteArrayClassLoader.class.getClassLoader());
        }

        public synchronized Class<?> getClass(String name, byte[] code) {
            if (name == null) {
                throw new IllegalArgumentException("");
            }
            return defineClass(name, code, 0, code.length);
        }

    }

}

2. Test results

Create JDK Proxy: 9 ms
Create CGLIB Proxy: 149 ms
Create JAVAASSIST Proxy: 115 ms
Create JAVAASSIST Bytecode Proxy: 58 ms
Create ASM Proxy: 1 ms
================
Run JDK Proxy: 479 ms, 104,384,000 t/s
Run CGLIB Proxy: 541 ms, 92,421,000 t/s
Run JAVAASSIST Proxy: 754 ms, 66,312,000 t/s
Run JAVAASSIST Bytecode Proxy: 194 ms, 257,731,000 t/s
Run ASM Bytecode Proxy: 202 ms, 247,524,000 t/s
----------------
Run JDK Proxy: 404 ms, 123,762,000 t/s
Run CGLIB Proxy: 325 ms, 153,846,000 t/s
Run JAVAASSIST Proxy: 681 ms, 73,421,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 180 ms, 277,777,000 t/s
----------------
Run JDK Proxy: 381 ms, 131,233,000 t/s
Run CGLIB Proxy: 339 ms, 147,492,000 t/s
Run JAVAASSIST Proxy: 674 ms, 74,183,000 t/s
Run JAVAASSIST Bytecode Proxy: 179 ms, 279,329,000 t/s
Run ASM Bytecode Proxy: 181 ms, 276,243,000 t/s
----------------

material:

Performance Comparison of Dynamic Proxy Solutions- Liang Fei's Blog- ITeye Blog

GitHub - wwadge/bonecp: BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but SHOULD NOW BE CONSIDERED DEPRECATED in favour of HikariCP.

GitHub - brettwooldridge/HikariCP: 光 HikariCP・A solid, high-performance, JDBC connection pool at last.

Questions about JDK dynamic proxy and CGLib dynamic proxy - Programmer Interview Experience Sharing Blog - CSDN Blog

02 ConcurrentBag and FastList Analysis of Hikari Source Code Analysis

Database connection pool performance comparison (hikari druid c3p0 dbcp jdbc)_c3p0 and hikari which is better

https://www.cnblogs.com/flyingeagle/articles/7102282.html

Use Javassist to dynamically create, modify and proxy classes - personal space in the name of algorithms - OSCHINA - Chinese Open Source Technology Exchange Community

Guess you like

Origin blog.csdn.net/lly576403061/article/details/130093245