目录
7.3、使用 javassist 直接修改 class 文件生成动态代理
8、JDK 、CGLib 、ASM 、Javassist 性能测试
引言
咱们开发项目的过程中用到很多的开源数据库链接池,比如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
----------------
资料:
JDK动态代理与CGLib动态代理相关问题_程序员面试经验分享的博客-CSDN博客
02Hikari源码解析之ConcurrentBag、FastList分析_concurrentbag解析_一直打铁的博客-CSDN博客
数据库连接池性能比对(hikari druid c3p0 dbcp jdbc)_c3p0和hikari那个好_把酒问天的博客-CSDN博客