SpringBoot 默认数据库连接池 HikariCP

news2024/12/23 18:49:39

目录

 引言

1、问题描述

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

3、HikariCP是什么

4、测试依赖

5、配置文件

5.1、数据库连接参数

5.2、连接池数据基本参数

5.3、连接检查参数

5.4、事务相关参数

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 - 中文开源技术交流社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Observability:使用 OpenTelemetry 和 Elastic 监控 OpenAI API 和 GPT 模型

作者&#xff1a;David Hope ChatGPT 现在很火&#xff0c;它打破了互联网。 作为 ChatGPT 的狂热用户和 ChatGPT 应用程序的开发者&#xff0c;我对这项技术的可能性感到无比兴奋。 我看到的情况是&#xff0c;基于 ChatGPT 的解决方案将呈指数级增长&#xff0c;人们将需要监…

Shiro概述

文章目录1.权限的管理1.1 什么是权限管理1.2 什么是身份认证1.3 什么是授权2.Shiro概述2.1 什么是Shiro2.2 Shiro 与 SpringSecurity 的对比2.3 基本功能3.shiro的核心架构4.shiro中的认证4.1 认证4.2 shiro中认证的关键对象4.3 身份认证流程4.4.登录认证实例4.5 自定义Realm5.…

Python标记数组的连通域

文章目录连通域标记structure参数操作连通域定位连通域连通域标记 通过label函数&#xff0c;可以对数组中的连通区域进行标注&#xff0c;效果如下 from scipy.ndimage import label import numpy as np a np.array([[0,0,1,1,0,0],[0,0,0,1,0,0],[1,1,0,0,1,0],[0,0,0,1,0…

虚拟机里安装ubuntu-23.04-beta-desktop-amd64,开启SSH(换源、备份),配置中文以及中文输入法

一、下载 官网 清华镜像站(推荐) 二、配置虚拟机 【自定义】 点击“下一步”&#xff0c;此处【默认】&#xff0c;再点击“下一步”。 点击“稍后安装操作系统”&#xff0c;再点击“下一步”。 点击“Linux(L)”&#xff0c;版本选择【Ubuntu 64 位】&#xff0c;再点击…

轻量级网页RSS阅读器selfoss

什么是 selfoss &#xff1f; selfoss 是一个多用途的 RSS 阅读器和提要聚合 Web 应用程序。它使您可以在一个地方轻松关注来自不同网站、社交网络和其他平台的更新。它是用 PHP 编写的&#xff0c;基本上可以让您在任何地方运行它。 安装 在群晖上以 Docker 方式安装。 在注…

【前沿技术】问答pk【ChatGPT Vs Notion AI Vs BAT AI 】

目录 写在前面 问题&#xff1a; 1 ChatGPT 1.1 截图 ​1.2 文字版 2 Notion AI 2.1 截图 2.2 文字版 3 BAT AI 3.1 截图 3.2 文字版 总结 序言 所有幸运和巧合的事&#xff0c;要么是上天注定&#xff0c;要么是一个人偷偷的在努力。 突发奇想&#xff0c;问三个…

机器学习---聚类算法

目录【写在前面】1、确认安装有scikit-learn库2、使用 make _ classification ()建立数据集3、使用模型进行分类头文件汇总亲和力传播聚合聚类BIRCH 聚类DBSCAN【本人的毕业设计系统中有用到】K-均值高斯混合模型【写在最后】【写在前面】 sklearn和scikit-learn&#xff1a; …

软件测试需要学什么

软件测试近些年也是比较热门的行业&#xff0c;薪资高、入门门槛低&#xff0c;让很多开发人员想纷纷加入软件开发这个行业&#xff0c;想要成为这一岗位的一员&#xff0c;想要进入软件测试行业&#xff0c;他们需要学习什么呢&#xff1f; 软件测试需要学习的还挺多的&#…

Flowable开源版和Flowable商业版有什么区别?

Flowable除了提供开源版本flowable-engine&#xff0c;它还提供了一系列基于Flowable引擎的快速、现代和完全可定制的企业产品&#xff08;商业收费&#xff09;&#xff1a;Flowable Work、Flowable Orchestrate和Flowable Engage。Flowable的开源版本和商业版本有什么区别&am…

【产线事故】分享生产线事故发生的一次OOM

文章目录前言OutOfMemoryError出现的原因常见堆内存溢出的几种情况现象分析Mybatis源码分析情景复现总结前言 继上次线上CPU出现了报警&#xff0c;这次服务又开始整活了&#xff0c;风平浪静了没几天&#xff0c;看生产日志服务的运行的时候&#xff0c;频繁的出现OutOfMemor…

接口自动化测试如何做?测试老鸟总结,接口测试数据构造大全......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 接口参数的数据获取…

Linux复习 / 线程相关----线程概念与控制 QA梳理

文章目录前言Q&A线程概念Q&#xff1a;线程和进程的区别&#xff1f;&#xff08;为什么要有线程&#xff0c;从进程的角度说明这个问题&#xff09;Q&#xff1a;Linux是如何设计线程的&#xff1f;Q&#xff1a;学习了线程后&#xff0c;你能说说进程和线程最大的区别是什…

博客系统(后端编程)

这里还是这四个页面: 博客列表页 博客详情页 登录页 博客编辑页 一、准备工作: 1.引入依赖 引入mysql,servlet,jackson的依赖,并且把之前的前端页面拷贝进去. 2.创建目录 并且把相关代码复制进去. 此时目录就完成了!!! 3.复制前端代码 直接ctrlv我们之前的前端代码到web…

目标检测YOLO系列-YOLOV7运行步骤(推理、训练全过程)

下载源代码&#xff1a;点击下载 进入项目根目录并执行以下命令安装requirements.txt中的相关依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple官网下载权重yolov7.pt&#xff08;测试使用&#xff09;、yolov7-tiny.pt&#xff08;训练使用…

【C++】哈希表:开散列和闭散列

&#x1f4dd; 个人主页 &#xff1a;超人不会飞)&#x1f4d1; 本文收录专栏&#xff1a;《C的修行之路》&#x1f4ad; 如果本文对您有帮助&#xff0c;不妨点赞、收藏、关注支持博主&#xff0c;我们一起进步&#xff0c;共同成长&#xff01; 目录前言一、基于哈希表的两个…

Spring MVC请求处理流程分析

Spring MVC请求处理流程分析一 Spring MVC 请求处理流程二 Spring MVC 请求处理流程源码分析2.1架构图解2.2 重要时机点分析2.3核心步骤分析2.3.1 getHandler⽅法剖析2.3.2 getHandlerAdapter⽅法剖析2.3.3 ha.handle⽅法剖析2.3.4 processDispatchResult⽅法剖析三 Spring MVC…

Ruby2D总结

Ruby学习心得 学了几天&#xff0c;Ruby2D这个项目我差不多把教程里面的东西做完了&#xff0c;感觉还好&#xff0c;只要每天一有空的话就去做的话就可以快速做好一个项目&#xff0c;不过还是会有一点虚浮感&#xff0c;但学习也是一个不能拖的事情&#xff0c;所以为了平衡…

【SpringBoot2】SpringBoot运维实用篇

SpringBoot运维实用篇 YW-1.SpringBoot程序的打包与运行 ​ 刚开始做开发学习的小伙伴可能在有一个知识上面有错误的认知&#xff0c;我们天天写程序是在Idea下写的&#xff0c;运行也是在Idea下运行的。 ​ 但是实际开发完成后&#xff0c;我们的项目是不可能运行在自己的电…

Java——树的子结构

题目链接 牛客在线oj题——树的子结构 题目描述 输入两棵二叉树A&#xff0c;B&#xff0c;判断B是不是A的子结构。&#xff08;我们约定空树不是任意一个树的子结构&#xff09; 假如给定A为{8,8,7,9,2,#,#,#,#,4,7}&#xff0c;B为{8,9,2}&#xff0c;2个树的结构如下&am…

【C++】引用(上)【深度全面解析】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…