本文是基于CGLIB 3.1進(jìn)行探究的
cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.
在Spring AOP中奖慌,通常會(huì)用它來生成AopProxy對(duì)象。不僅如此费尽,在Hibernate中PO(Persistant Object 持久化對(duì)象)字節(jié)碼的生成工作也要靠它來完成究孕。
本文將深入探究CGLIB動(dòng)態(tài)代理的實(shí)現(xiàn)機(jī)制枫虏,配合下面這篇文章一起食用口味更佳:
深入理解JDK動(dòng)態(tài)代理機(jī)制
一柒巫、CGLIB動(dòng)態(tài)代理示例
下面由一個(gè)簡單的示例開始我們對(duì)CGLIB動(dòng)態(tài)代理的介紹:
JDK代理要求被代理的類必須實(shí)現(xiàn)接口,有很強(qiáng)的局限性补憾。而CGLIB動(dòng)態(tài)代理則沒有此類強(qiáng)制性要求。簡單的說卷员,CGLIB會(huì)讓生成的代理類繼承被代理類盈匾,并在代理類中對(duì)代理方法進(jìn)行強(qiáng)化處理(前置處理、后置處理等)毕骡。在CGLIB底層削饵,其實(shí)是借助了ASM這個(gè)非常強(qiáng)大的Java字節(jié)碼生成框架。
二挺峡、生成代理類對(duì)象
從圖1.3中我們看到葵孤,代理類對(duì)象是由Enhancer類創(chuàng)建的。Enhancer是CGLIB的字節(jié)碼增強(qiáng)器橱赠,可以很方便的對(duì)類進(jìn)行拓展尤仍,如圖1.3中的為類設(shè)置Superclass。
創(chuàng)建代理對(duì)象的幾個(gè)步驟:
- 生成代理類的二進(jìn)制字節(jié)碼文件狭姨;
- 加載二進(jìn)制字節(jié)碼宰啦,生成Class對(duì)象( 例如使用Class.forName()方法 );
- 通過反射機(jī)制獲得實(shí)例構(gòu)造饼拍,并創(chuàng)建代理類對(duì)象
我們來看看將代理類Class文件反編譯之后的Java代碼
package proxy;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06
extends HelloServiceImpl
implements Factory
{
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$sayHello$0$Method;
private static final MethodProxy CGLIB$sayHello$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1()
{
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class localClass1 = Class.forName("proxy.HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06");
Class localClass2;
Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = tmp95_92[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
Method[] tmp115_95 = tmp95_92;
CGLIB$equals$2$Method = tmp115_95[1];
CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
Method[] tmp135_115 = tmp115_95;
CGLIB$toString$3$Method = tmp135_115[2];
CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
Method[] tmp155_135 = tmp135_115;
CGLIB$hashCode$4$Method = tmp155_135[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
Method[] tmp175_155 = tmp155_135;
CGLIB$clone$5$Method = tmp175_155[4];
CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
tmp175_155;
Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "sayHello", "()V" }, (localClass2 = Class.forName("proxy.HelloServiceImpl")).getDeclaredMethods());
CGLIB$sayHello$0$Method = tmp223_220[0];
CGLIB$sayHello$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "sayHello", "CGLIB$sayHello$0");
tmp223_220;
return;
}
final void CGLIB$sayHello$0()
{
super.sayHello();
}
public final void sayHello()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
if (this.CGLIB$CALLBACK_0 != null) {
return;
}
super.sayHello();
}
final void CGLIB$finalize$1()
throws Throwable
{
super.finalize();
}
protected final void finalize()
throws Throwable
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
if (this.CGLIB$CALLBACK_0 != null) {
return;
}
super.finalize();
}
final boolean CGLIB$equals$2(Object paramObject)
{
return super.equals(paramObject);
}
public final boolean equals(Object paramObject)
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null)
{
Object tmp41_36 = tmp17_14.intercept(this, CGLIB$equals$2$Method, new Object[] { paramObject }, CGLIB$equals$2$Proxy);
tmp41_36;
return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue();
}
return super.equals(paramObject);
}
final String CGLIB$toString$3()
{
return super.toString();
}
public final String toString()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null) {
return (String)tmp17_14.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);
}
return super.toString();
}
final int CGLIB$hashCode$4()
{
return super.hashCode();
}
public final int hashCode()
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null)
{
Object tmp36_31 = tmp17_14.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
tmp36_31;
return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
}
return super.hashCode();
}
final Object CGLIB$clone$5()
throws CloneNotSupportedException
{
return super.clone();
}
protected final Object clone()
throws CloneNotSupportedException
{
MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
if (tmp4_1 == null)
{
tmp4_1;
CGLIB$BIND_CALLBACKS(this);
}
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null) {
return tmp17_14.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);
}
return super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature paramSignature)
{
String tmp4_1 = paramSignature.toString();
switch (tmp4_1.hashCode())
{
case -1574182249:
if (tmp4_1.equals("finalize()V")) {
return CGLIB$finalize$1$Proxy;
}
break;
}
}
public HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06()
{
CGLIB$BIND_CALLBACKS(this);
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)
{
CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] paramArrayOfCallback)
{
CGLIB$STATIC_CALLBACKS = paramArrayOfCallback;
}
private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
{
82ef2d06 local82ef2d06 = (82ef2d06)paramObject;
if (!local82ef2d06.CGLIB$BOUND)
{
local82ef2d06.CGLIB$BOUND = true;
Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
if (tmp23_20 == null)
{
tmp23_20;
CGLIB$STATIC_CALLBACKS;
}
local82ef2d06.CGLIB$CALLBACK_0 = (// INTERNAL ERROR //
三赡模、對(duì)委托類進(jìn)行代理
我們上面貼出了生成的代理類源碼。以我們上面的例子為參考师抄,下面我們總結(jié)一下CGLIB在進(jìn)行代理的時(shí)候都進(jìn)行了哪些工作呢
- 生成的代理類HelloServiceImpl$$EnhancerByCGLIB$$82ef2d06繼承被代理類HelloServiceImpl漓柑。在這里我們需要注意一點(diǎn):如果委托類被final修飾,那么它不可被繼承叨吮,即不可被代理辆布;同樣,如果委托類中存在final修飾的方法茶鉴,那么該方法也不可被代理锋玲;
- 代理類會(huì)為委托方法生成兩個(gè)方法,一個(gè)是重寫的sayHello方法涵叮,另一個(gè)是CGLIB$sayHello$0方法惭蹂,我們可以看到它是直接調(diào)用父類的sayHello方法;
- 當(dāng)執(zhí)行代理對(duì)象的sayHello方法時(shí)割粮,會(huì)首先判斷一下是否存在實(shí)現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;盾碗,如果存在,則將調(diào)用MethodInterceptor中的intercept方法舀瓢,如圖2.1廷雅。
在intercept方法中,我們除了會(huì)調(diào)用委托方法,還會(huì)進(jìn)行一些增強(qiáng)操作榜轿。在Spring AOP中幽歼,典型的應(yīng)用場景就是在某些敏感方法執(zhí)行前后進(jìn)行操作日志記錄。
我們從圖2.1中看到谬盐,調(diào)用委托方法是通過代理方法的MethodProxy對(duì)象調(diào)用invokeSuper方法來執(zhí)行的甸私,下面我們看看invokeSuper方法中的玄機(jī):
在這里好像不能直接看出代理方法的調(diào)用。沒關(guān)系飞傀,我會(huì)慢慢介紹皇型。
我們知道,在JDK動(dòng)態(tài)代理中方法的調(diào)用是通過反射來完成的砸烦。如果有對(duì)此不太了解的同學(xué)弃鸦,可以看下我之前的博客----深入理解JDK動(dòng)態(tài)代理機(jī)制。但是在CGLIB中幢痘,方法的調(diào)用并不是通過反射來完成的唬格,而是直接對(duì)方法進(jìn)行調(diào)用:FastClass對(duì)Class對(duì)象進(jìn)行特別的處理,比如將會(huì)用數(shù)組保存method的引用颜说,每次調(diào)用方法的時(shí)候都是通過一個(gè)index下標(biāo)來保持對(duì)方法的引用购岗。比如下面的getIndex方法就是通過方法簽名來獲得方法在存儲(chǔ)了Class信息的數(shù)組中的下標(biāo)。
以我們上面的sayHello方法為例门粪,f1指向委托類對(duì)象喊积,f2指向代理類對(duì)象,i1和i2分別代表著sayHello方法以及CGLIB$sayHello$0方法在對(duì)象信息數(shù)組中的下標(biāo)玄妈。
到此為止CGLIB動(dòng)態(tài)代理機(jī)制就介紹完了乾吻,下面給出三種代理方式之間對(duì)比。
代理方式 | 實(shí)現(xiàn) | 優(yōu)點(diǎn) | 缺點(diǎn) | 特點(diǎn) |
---|---|---|---|---|
JDK靜態(tài)代理 | 代理類與委托類實(shí)現(xiàn)同一接口拟蜻,并且在代理類中需要硬編碼接口 | 實(shí)現(xiàn)簡單绎签,容易理解 | 代理類需要硬編碼接口,在實(shí)際應(yīng)用中可能會(huì)導(dǎo)致重復(fù)編碼瞭郑,浪費(fèi)存儲(chǔ)空間并且效率很低 | 好像沒啥特點(diǎn) |
JDK動(dòng)態(tài)代理 | 代理類與委托類實(shí)現(xiàn)同一接口辜御,主要是通過代理類實(shí)現(xiàn)InvocationHandler并重寫invoke方法來進(jìn)行動(dòng)態(tài)代理的鸭你,在invoke方法中將對(duì)方法進(jìn)行增強(qiáng)處理 | 不需要硬編碼接口屈张,代碼復(fù)用率高 | 只能夠代理實(shí)現(xiàn)了接口的委托類 | 底層使用反射機(jī)制進(jìn)行方法的調(diào)用 |
CGLIB動(dòng)態(tài)代理 | 代理類將委托類作為自己的父類并為其中的非final委托方法創(chuàng)建兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法袱巨,它在方法中會(huì)通過super調(diào)用委托方法阁谆;另一個(gè)是代理類獨(dú)有的方法。在代理方法中愉老,它會(huì)判斷是否存在實(shí)現(xiàn)了MethodInterceptor接口的對(duì)象场绿,若存在則將調(diào)用intercept方法對(duì)委托方法進(jìn)行代理 | 可以在運(yùn)行時(shí)對(duì)類或者是接口進(jìn)行增強(qiáng)操作,且委托類無需實(shí)現(xiàn)接口 | 不能對(duì)final類以及final方法進(jìn)行代理 | 底層將方法全部存入一個(gè)數(shù)組中嫉入,通過數(shù)組索引直接進(jìn)行方法調(diào)用 |