書接上文,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反射功能。