动态代理系列Cglib的FastClass机制(四)
书接上文,https://www.cnblogs.com/lyhero11/p/15553458.html
Cglib代理类分析
上回书遗留了一个疑问:cglib是如何动态的对委托类的方法进行调用的,我们说由于Java反射的一些性能问题,cglib使用了一种叫做FastClass的技巧来优化这个调用。
接下来分析下Cglib生成的代理类,来研究一下所谓的FastClass机制。
先在设置一下代理类的输出路径:
//设置cglib生成的代理类输出到temp文件夹
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\temp");
然后就可以去D:\temp用反编译工具看生成的代理类的源码了,推荐使用Luyten反编译,jd-gui有些代码反编译不出来。
如图可以看到对委托类LancerEvolutionVI动态了3个类:
先来看一下LancerEvolutionVI$$EnhancerByCGLIB$$9332002c,这个就是我们的对象对应的代理类:
public class LancerEvolutionVI$$EnhancerByCGLIB$$9332002c extends LancerEvolutionVI implements Factory{
final void CGLIB$speed$0() {
super.speed();
}
public final void speed() {
MethodInterceptor loc_1;
MethodInterceptor loc_0;
//方法拦截器,对应我们的CarMethodInterceptor
if ((loc_0 = (loc_1 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
loc_1 = (loc_0 = this.CGLIB$CALLBACK_0);
}
if (loc_0 != null) {
//执行拦截方法,实现代理,对应我们实现MethodInterceptor接口的intercept方法
loc_1.intercept((Object)this, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Method, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$emptyArgs, LancerEvolutionVI$$EnhancerByCGLIB$$9332002c.CGLIB$speed$0$Proxy);
return;
}
super.speed();
}
}
然后,Object ret = methodProxy.invokeSuper(proxyObj, args)里我们用的invokeSuper这个方法,其实理论上这里我们有了proxyObj也就是LancerEvolutionVI$$EnhancerByCGLIB代理对象,那么其实可以直接调用CGLIB$speed$0()来调用父类也就是委托类的方法了,为什么要用invokeSuper呢?接着看MethodProxy.invokeSuper方法的源代码:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args); //fci.i2是fastClass里边存的Method的索引
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
传说中的FastClass出现了,简单理解一下FastClass:为一个对象A创建它的FastClass对象,这个FastClass对象相当于A的方法索引,根据A的方法名生成并关联一个index、每个index对应A的一个方法。后续只要根据这个index以及A的实例,就可以调用fastClass的invoke(instanceOfA, index, args)
方法来快速的调用A的方法了。实现了Java反射的“运行时动态调用指定类的方法”的功能,但是使用了不同的机制。我们知道,java反射调用方法是比较“重”的操作,要经过一系列的权限验证、通过native方法请求jvm去方法区查找方法定义、以及最后的invoke仍然可能要通过JNI调用native方法。而相比之下,FastClass方式则跟一般的一个普通的对象方法调用没啥区别、只是多了一步根据index判断调用委托类的哪个方法这一步骤、性能损耗基本没有。
仿写一个例子,加深一下FastClass技巧的印象:
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 为每个委托类动态生成FastClass类
* */
@Slf4j
public class DummyServiceFactClass {
//sign方法签名
public int getIndex(String sign){
switch (sign){
case "service1":
return 1;
case "service2":
return 2;
}
return 0;
}
//index方法索引, obj调用对象, args方法参数
public Object invoke(int index, Object obj, Object[] args){
DummyService dummyService = (DummyService) obj;
switch (index){
case 1:
return dummyService.service1((String)args[0]);
case 2:
dummyService.service2((String)args[0]);
return null;
}
return null;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
DummyService dummyService = new DummyService();
//使用FastClass
DummyServiceFactClass dummyServiceFactClass = new DummyServiceFactClass();
int index1 = dummyServiceFactClass.getIndex("service1");
int index2 = dummyServiceFactClass.getIndex("service2");
Object ret1 = dummyServiceFactClass.invoke(1, dummyService, new Object[]{"123"});
Object ret2 = dummyServiceFactClass.invoke(2, dummyService, new Object[]{"456"});
log.info("ret1 = {}, ret2 = {}", ret1, ret2);
//使用反射
Class clazz = dummyService.getClass();
Method method1 = clazz.getDeclaredMethod("service1", String.class);
Method method2 = clazz.getDeclaredMethod("service2", String.class);
ret1 = method1.invoke(dummyService,"123f");
ret2 = method2.invoke(dummyService, "345f");
log.info("ret1 = {}, ret2 = {}", ret1, ret2);
}
}
输出
10:40:32.174 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 456
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123, ret2 = null
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service1 , param = 123f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyService - service2, param = 345f
10:40:32.178 [main] INFO com.wangan.springbootone.aop.DummyServiceFactClass - ret1 = this is service1, param = 123f, ret2 = null
总结
FastClass机制算是一种技巧层面的东西,在java内存里边维护一个index值和对象的方法之间的逻辑映射,然后运行期可以根据index和实例来动态调用方法、且不用使用比较“重”的Java反射功能。