Cglib原始碼分析 invoke和invokeSuper的差別
Cglib的例項
本文重在原始碼的分析,Cglib的使用不再複述。
//被代理類
public class InfoDemo {
public void welcome (String person){
System.out.println("welcome :" + person);
}
}
public class CglibInfoProxy implements MethodInterceptor {
private Object target;
public Object newInstance(Object source){
target = source;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method!!!" );
Object value = methodProxy.invokeSuper(o, objects);
//Object value = methodProxy.invoke(o, objects);
return value;
}
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\\\classes");
InfoDemo instance = (InfoDemo) new CglibInfoProxy().newInstance(new InfoDemo());
instance.welcome("zhangsan");
}
}
代理類位元組碼分析
先來看main函式中new CglibInfoProxy().newInstance(new InfoDemo()),簡單來說這個方法是將infoDemo作為一個父類,通過asm位元組碼生成一個子類來繼承infoDemo。
InfoDemo$$EnhancerByCGLIB$$8b8da05b.class
,就是生成的子類的位元組碼,稱這個子類為InfoDemo的代理類。擷取部分位元組碼顯示:
public class InfoDemo$$EnhancerByCGLIB$$8b8da05b extends InfoDemo implements Factory {
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$welcome$0$Method;
private static final MethodProxy CGLIB$welcome$0$Proxy;
//..........................................省略
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("CglibTest.InfoDemo$$EnhancerByCGLIB$$8b8da05b");
Class var1;
CGLIB$welcome$0$Method = ReflectUtils.findMethods(new String[]{"welcome", "(Ljava/lang/String;)V"}, (var1 = Class.forName("CglibTest.InfoDemo")).getDeclaredMethods())[0];
CGLIB$welcome$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
//..........................................省略
}
final void CGLIB$welcome$0(String var1) {
super.welcome(var1);
}
//先來判斷這個代理類中是否設定了方法攔截。如果設定了就呼叫該攔截器的intercept方法。
//在本程式中,我們是設定了攔截器的。enhancer.setCallback(this);
public final void welcome(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy);
} else {
super.welcome(var1);
}
}
//..........................................省略
}
在這個代理類中,這裡有兩點需要注意。
1.它會將父類中的每一個方法,生成兩個與之對應。如父類中的welcome,在代理類中就會有CGLIB0,welcome的兩個方法與之對應。
2.每一個方法都會靜態塊中,經過MethodProxy.create生成對應的方法代理。如
MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "welcome", "CGLIB$welcome$0");
當main函式執行到instance.welcome(“zhangsan”);這個語句時,會進入到代理類中的public final void welcome(String var1) 方法。進而執行了var10000.intercept()方法。
根據CglibInfoProxy中的intercept,先是會輸出一句“before method!!!”,然後呼叫methodProxy.invokeSuper(o, objects);
這裡的methodProxy對應的是var10000.intercept(this, CGLIB$welcome$0$Method, new Object[]{var1}, CGLIB$welcome$0$Proxy)
中的CGLIB$welcome$0$Proxy。
那麼就相當於執行CGLIB$welcome$0$Proxy.invokeSuper(o, objects)
,那麼來看看invokeSuper在做什麼。
MethodProxy 原始碼分析
public class MethodProxy {
//下面的前三個變數在create方法中,都已經得到了初始值了。
private Signature sig1;
private Signature sig2;
private MethodProxy.CreateInfo createInfo;
//FastClassInfo是在呼叫methodProxy.invoke或者methodProxy.invokeSuper中,init()會觸發,後面再來細看這個。
private volatile MethodProxy.FastClassInfo fastClassInfo;
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);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
if(this.fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " + this.sig1);
} else {
throw var5;
}
}
}
//本質就是要生成一個fastClassInfo,fastClassInfo裡面存放著兩個fastclass,f1,f2。
//還有兩個方法索引的值i1,i2。
private void init() {
if(this.fastClassInfo == null) {
Object var1 = this.initLock;
synchronized(this.initLock) {
if(this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
//難道每一個方法,我們都去生成一個fastclass嗎?
//不是的,每一個方法的fastclass都是一樣的,只不過他們的i1,i2不一樣。如果快取中就取出,沒有就生成新的FastClass
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
//根據一個類的資訊,返回的該物件的一個Fastclass
private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
Generator g = new Generator();
g.setType(type);
g.setClassLoader(ci.c2.getClassLoader());
g.setNamingPolicy(ci.namingPolicy);
g.setStrategy(ci.strategy);
g.setAttemptLoad(ci.attemptLoad);
return g.create();
}
}
fci.f2.invoke(fci.i2, obj, args);fci存放了兩個類的fastClass。
其中f1是被代理的類對應的是InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class,
f2InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c.class
這也就是為什麼,在classes檔案中會生成三個class檔案了,一個代理類,兩個fastclass.
f2.invoke,那就說明會呼叫InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c
這個類的方法。
代理類的FastClass
那再看看代理類的fastClass的位元組碼長啥樣
貼出InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c
的部分位元組碼:
public class InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c extends FastClass {
public InfoDemo$$EnhancerByCGLIB$$8b8da05b$$FastClassByCGLIB$$9065a6c(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -2055565910:
if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
return 12;
}
break;
case -1725733088:
if(var10000.equals("getClass()Ljava/lang/Class;")) {
return 24;
}
case 1013143764:
if(var10000.equals("CGLIB$welcome$0(Ljava/lang/String;)V")) {
return 17;
}
}
//----省略
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
8b8da05b var10000 = (8b8da05b)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return var10000.toString();
case 1:
return new Integer(var10000.hashCode());
case 17:
var10000.CGLIB$welcome$0((String)var3[0]);
}
//----省略
}
}
}
可以看到,會執行到voke方法中的
var10000.CGLIB$welcome$0((String)var3[0])
;,而CGLIB$welcome$0
其實就是直接呼叫了super.welcome(var1)的方法。輸出結束之後就會執行完畢。
那麼,如果現在呼叫的是methodProxy.invoke(o, objects);而不是invokeSuper會是怎麼樣的情況呢?
通過上面MethodProxy的原始碼,可以看到當執行invoke的時候會執行到fci.f1.invoke(fci.i1, obj, args);
即會呼叫被代理類的fastclass,InfoDemo$$FastClassByCGLIB$$f4c7f3ac.class
中的invoke。
開啟該class檔案我們會發現,執行的的是welcome()。
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
switch(var10001) {
case 0:
var10000.welcome((String)var3[0]);
return null;
case 1:
}
}
那不就是和在main函式中instance.welcome(“zhangsan”)一樣的步驟了嘛,就會又開始迴圈呼叫,一直到棧溢位報錯。所以,invoke會造成OOM的問題。
相關推薦
Cglib原始碼分析 invoke和invokeSuper的差別
Cglib的例項 本文重在原始碼的分析,Cglib的使用不再複述。 //被代理類 public class InfoDemo { public void welcome (String person){ System.out.prin
cglib invoke 和 invokeSuper 可用的組合
html hello exc 場景 怎麽 java 總結 ML rgs 在深入字節碼理解invokeSuper無限循環的原因中,我們理解的cglib的原理和其中一個合理的調用方式。但是這個調用方式是基於類的,對所有實例生效。實際場景中,我們可能只是希望代理某個具體的實例,而
dubbo原始碼分析-Directory 和 LoadBalance-筆記
Directory 訂閱節點的變化, 當zookeeper上指定節點發生變化以後,會通知到RegistryDirectory的notify方法 將url轉化為invoker物件 呼叫過程中invokers的使用 StaticDirectory: 靜態目
原始碼分析commitAllowingStateLoss() 和commit()的區別
之前在使用Fragment的時候偶爾會有這麼一個報錯,Can not perform this action after onSaveInstanceState,意思為無法再onSaveInstanceState之後執行該操作,這個操作就是指commit(),之前也沒怎麼在意
JDK原始碼分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
招賢納士: 我們叫數瀾 我們核心技術團隊來自阿里、華為、金蝶、移動、GE等 我們獲得來自阿里巴巴集團聯合創始人、湖畔山南總裁謝世煌、IDG合夥人牛奎光、洪泰等投資 我們的官網:https://www.dtwave.com 我們提供:期權、五險一金、試用期全薪、商業保險、免
Libevent原始碼分析-----日誌和錯誤處理
日誌處理: 在Libevent的原始碼中,經常會見到形如event_warn、event_msgx、event_err之類的函式。這通常出現在程式碼中一些值是不合理時。這些函式就是Libevent的日誌函式。它能把這些不合理的情況打印出來,告知使用者
Zeppelin 原始碼分析-排程和資源分析(2)
Scheduler 類 Scheduler 類是排程類的抽象類,其中定義了很多關鍵方法,比如 submit 方法等,並且是一個執行緒類,一直執行在主程序或者獨立 JVM 程序。 RemoteScheduler 類 RemoteScheduler
Java分散式跟蹤系統Zipkin(二):Brave原始碼分析-Tracer和Span
Brave是Java版的Zipkin客戶端,它將收集的跟蹤資訊,以Span的形式上報給Zipkin系統。 (Zipkin是基於Google的一篇論文,名為Dapper,Dapper在荷蘭語裡是“勇敢的”的意思,這也是Brave的命名的原因) 我們一般
Android 之 三級快取(記憶體!!!、本地、網路)及記憶體LruCache擴充套件 及原始碼分析--- 學習和程式碼講解
一. 三級快取簡介 如上圖所示,目前App中UI介面經常會涉及到圖片,特別是像“今日關注”新聞這類app中,圖片運用的機率十分頻繁。當手機上需要顯示大量圖片類似listView、gridView控制元件並且使用者會上下滑動,即將瀏覽過的圖片又載入一遍,
Libevent原始碼分析-timer和signal處理
timer處理 evtimer_set(&ev, time_cb, NULL);//設定定時器事件 其中evtimer_set是個巨集定義 #define evtimer_set(ev, cb, arg) event_set
Java分散式跟蹤系統Zipkin(五):Brave原始碼分析-Brave和SpringMVC整合
上一篇博文中,我們分析了Brave是如何在普通Web專案中使用的,這一篇博文我們繼續分析Brave和SpringMVC專案的整合方法及原理。 我們分兩個部分來介紹和SpringMVC的整合,及XML配置方式和Annotation註解方式 pom.xml新
深入原始碼分析TreeSet和TreeMap
類似於前面介紹的HashMap和HashSet之間的關係,HashSet底層依賴於HashMap實現,而TreeSet底層則採用一個NavigableMap來儲存TreeSet集合的元素。但實際上,由於NavigableMap只是一個介面,因底層依然是使用Tre
spring註解原始碼分析-解析和注入註解配置的資源
類內部的註解,如@Autowire、@Value、@Required、@Resource以及EJB和WebService相關的註解,是容器對Bean例項化和依賴注入時,通過容器中註冊的Bean後置處理處理這些註解的。 當使用Spring的註解功能時, <cont
Android 從原始碼分析Bitmap和BitmapFactory常用API
Bitmap recycle() 呼叫nativeRecycle()釋放該bitmap分配的native物件,清除對畫素資料的引用。不會同步的釋放畫素資料,只是簡單的允許bitmap在沒有其他引用時被垃圾回收。此bitmap被標記為dead,意味著呼叫get
spring 註解原始碼分析-掃描和讀取bean定義
1.概述 從spring2.0以後的版本中,spring也引入了基於註解方式的配置,註解是jdk1.5中引入的一個新特性,用於簡化Bean的配置,某些場合可以取代xml配置檔案。 Spring IoC容器對於類級別的註解和類內部的註解分以下兩種策略: (1)類級別的註解
[原始碼分析]ArrayList和LinkedList如何實現的?我看你還有機會!
> 文章已經收錄在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程式設計師所需要掌握的核心知識,歡迎Star和指教。 > 歡迎關注我的[公眾號](https://github.com/n
Tomcat詳解系列(3) - 原始碼分析準備和分析入口
# Tomcat - 原始碼分析準備和分析入口 > 上文我們介紹了Tomcat的架構設計,接下來我們便可以下載原始碼以及尋找原始碼入口了。@pdai ## 原始碼下載和編譯 首先是去官網下載Tomcat的原始碼和二進位制安裝包,我這裡分析最新的[Tomcat9.0.39穩定版本](https://tomca
Spring原始碼分析總結(三)-JDK動態代理和CGLIB代理以及攔截器
一、JDK動態代理和CGLIB代理方式: 1、如果目標物件實現了介面,預設會採用JDK代理實現AOP,也可以通過配置強制使用CGLIB實現 2、如果目標物件沒有實現介面,必須採用CGLIB庫,Spring會自動在JDK方式和CGLIB方式之前轉
Mybatis原始碼分析(4)—— Mapper的建立和獲取
Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用? 就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的
NSQ原始碼分析(四)——inFlightPqueue和PriorityQueue優先順序佇列
在Channel結構體中用到了兩種優先順序佇列pqueue.PriorityQueue和inFlightPqueue。 deferredMessages map[MessageID]*pqueue.Item deferredPQ pqueue.Priorit