SpringBoot 默认数据库连接池 HikariCP

目录

 引言

1、问题描述

2、SpringBoot默认的数据库连接池

3、HikariCP是什么

4、测试依赖

5、配置文件

5.1、数据库连接参数

5.2、连接池数据基本参数

5.3、连接检查参数

5.4、事务相关参数

扫描二维码关注公众号,回复: 14887568 查看本文章

5.5、JMX参数

6、HikariCP源码浅析

6.1、HikariConfig--连接池配置的加载

6.2、HikariPool--连接池

1、HikariPool UML图

2、PoolBase

3、HikariPool

4、如何获取一个链接对象

6.3、ConcurrentBag--更少的锁冲突

7、HikariCP为什么快?

7.1、通过代码设计和优化大幅减少线程间的锁竞争

7.2、引入了更多 JDK 的特性

7.3、使用 javassist 直接修改 class 文件生成动态代理

8、JDK 、CGLib 、ASM 、Javassist 性能测试

1、测试代码

2、测试结果


 引言

        咱们开发项目的过程中用到很多的开源数据库链接池,比如druid、c3p0、BoneCP等等,前端时间在部署新服务的时候发现了个问题,排查完毕问题正好学习学习SpringBoot的默认的数据库连接池HikariCP的一些知识。HikariCP官网地址: https://github.com/brettwooldridge/HikariCP

1、问题描述

        我们新项目部署上线之后在观察日志的时候发现了这个警告,经过排查是发现DB方面的问题,保留现场如下。

2、SpringBoot默认的数据库连接池

        Spring-Boot-2.0.0-M1版本将默认的数据库连接池从tomcat jdbc pool改为了HikariCP。

3、HikariCP是什么

        HikariCP 是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能,另外,和 druid 一样,HikariCP 也支持监控功能。

        HikariCP 是目前最快的连接池,就连风靡一时的 BoneCP 也停止维护,主动让位给它,SpringBoot 也把它设置为默认连接池。

4、测试依赖

        既然官网说HikariCP是最快的数据库连接池,不妨我们进行一些尝试,验证一下官网放出的狠话。验证也比较简单,只需要在项目中添加依赖即可。

 <!-- 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、配置文件

        上面一步添加完依赖,接下来具体实操之前先了解一下HikariCP的各种配置信息。

5.1、数据库连接参数

        注意,这里url在后面拼接了多个参数用于避免乱码、时区报错问题。

#-------------基本属性--------------------------------
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、连接池数据基本参数

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

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

5.3、连接检查参数

        注意:针对连接失效的问题,HikariCP 强制开启借出测试和空闲测试,不开启回收测试,可选的只有泄露测试。所有的超时时间都可以根据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、事务相关参数

#-------------事务相关的属性--------------------------------
#当连接返回池中时是否设置自动提交
#默认为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参数

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

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

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

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

6、HikariCP源码浅析

6.1、HikariConfig--连接池配置的加载

        在HikariCP 中,HikariConfig用于加载配置,它的加载要更加简洁。直接从PropertyElf.setTargetFromProperties(Object, Properties)方法开始看。

// 这个方法就是将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--连接池

        HikariPool 是一个非常重要的类,它负责管理连接。

1、HikariPool UML图

HikariPoolMXBean:采用JMX控制HikariPool的入口。

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

2、PoolBase

        HikariPool链接池的配置信息。

3、HikariPool

        连接池的管理。

属性:

//配置信息。
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、如何获取一个链接对象

/**
 * 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--更少的锁冲突

        在 HikariCP 中ConcurrentBag用于存放PoolEntry对象(封装了Connection对象,IConcurrentBagEntry实现类),本质上可以将它就是一个资源池。

 属性:

//存放着当前线程返还的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;

方法:

        在以下方法中,唯一可能出现线程切换到就是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、HikariCP为什么快?

7.1、通过代码设计和优化大幅减少线程间的锁竞争

        1、元素状态的引入,以及使用CAS方法修改状态。在ConcurrentBag中,使用使用中、未使用、删除和保留等表示元素的状态,而不是使用不同的集合来维护不同状态的元素。元素状态这一概念的引入非常关键,为后面的几点提供了基础。 ConcurrentBag的方法中多处调用 CAS 方法来判断和修改元素状态,这一过程不需要加锁。

        2、threadList 的使用。当前线程归还的元素会被绑定到ThreadLocal,该线程再次获取元素时,在该元素未被偷走的前提下可直接获取到,不需要去 sharedList 遍历获取;

7.2、引入了更多 JDK 的特性

        尤其是 concurrent 包的工具。相比较于DBCP、C3P0等数据库链接池问世较晚,很方便的享受JDK的升级带来的方便。

        1、采用CopyOnWriteArrayList来存放元素。在CopyOnWriteArrayList中,读和写使用的是不同的数组,避免了两者的锁竞争,至于多个线程写入,则会加 ReentrantLock 锁。

        2、sharedList 的读写控制。borrow 和 requite 对 sharedList 来说都是不加锁的,缺点就是会牺牲一致性。用户线程无法进行增加元素的操作,只有 addConnectionExecutor 可以,而 addConnectionExecutor 只会开启一个线程执行任务,所以 add 操作不会存在锁竞争。至于 remove 是唯一会造成锁竞争的方法,这一点我认为也可以参照 addConnectionExecutor 来处理,在加入任务队列前把 PoolEntry 的状态标记为删除中。

7.3、使用 javassist 直接修改 class 文件生成动态代理

        1、使用 javassist 直接修改 class 文件生成动态代理,精简了很多不必要的字节码,提高代理方法运行速度。尤其JDK1.8优化以后JDK的动态代理,CGlib代理已经和javassist、asm等一个数量级。

8、JDK 、CGLib 、ASM 、Javassist 性能测试

        环境:JDK 1.8,CGLib 3.3.0, ASM JDK自带的ASM包,Javassist 3.26.0-GA。

        数据为执行三次,每次调用5千万次代理方法的结果。

1、测试代码

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、测试结果

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
----------------

资料:

动态代理方案性能对比 - 梁飞的博客 - ITeye博客

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.

JDK动态代理与CGLib动态代理相关问题_程序员面试经验分享的博客-CSDN博客

02Hikari源码解析之ConcurrentBag、FastList分析_concurrentbag解析_一直打铁的博客-CSDN博客

数据库连接池性能比对(hikari druid c3p0 dbcp jdbc)_c3p0和hikari那个好_把酒问天的博客-CSDN博客

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

使用Javassist来动态创建,修改和代理类 - 算法之名的个人空间 - OSCHINA - 中文开源技术交流社区

猜你喜欢

转载自blog.csdn.net/lly576403061/article/details/130093245