CGLIB的全稱是:Code Generation Library。
CGLIB是一個強大的、高效能、高質量的程式碼生成類庫,它可以在執行期擴充套件Java類與實現Java介面,
底層使用的是位元組碼處理框架ASM。
Github地址:https://github.com/cglib/cglib。
CGLIB的Maven座標如下所示:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
首先,新增一個類:
public class Coder {
public void work() {
System.out.println("認真寫bug……");
}
}
然後,自定義一個方法攔截器,實現net.sf.cglib.proxy.MethodInterceptor
介面並重寫intercept
方法:
public class AttendanceMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("上班打卡……");
Object result = proxy.invokeSuper(obj, args);
System.out.println("下班打卡……");
return result;
}
}
重點看下Object result = proxy.invokeSuper(obj, args);
,該行程式碼最終會執行真正的目標方法,在這前後,我們可以新增一些增強邏輯。
然後,新建個測試類,看下CGLIB動態代理如何使用:
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Coder.class);
enhancer.setCallback(new AttendanceMethodInterceptor());
// 建立代理物件
Object object = enhancer.create();
Coder coder = (Coder) object;
coder.work();
}
}
執行以上程式碼,效果如下圖所示:
從執行結果可以看出,在目標方法的前後,執行了自定義的操作。
看下上面的測試類程式碼,首先是建立了一個net.sf.cglib.proxy.Enhancer
物件,然後呼叫了setSuperclass()
方法
將enhancer物件的父類別設定為Coder類:
緊接著呼叫了setCallback()
方法將enhancer物件的方法攔截器設定為自定義的AttendanceMethodInterceptor:
然後是呼叫enhancer物件的create()
方法來生成一個代理物件。
先列印下,簡單看下這個代理類的資訊:
圖中的com.zwwhnly.mybatisplusdemo.cglibproxy.Coder$$EnhancerByCGLIB$$8e91f654
就是CGLIB生成的代理類的名稱。
那麼這個代理類具體是什麼樣子呢?
在上面的測試類程式碼中(Object object = enhancer.create();
程式碼之前)新增以下一行程式碼:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");
然後再次執行,會看到專案根目錄下生成了一個cglib資料夾,自動生成的代理類就包含在其中:
可以看到一共生成了5個類,這裡重點關注下紅色標記的3個類。
先看下Coder$$EnhancerByCGLIB$$8e91f654.class
,這個類就是自動生成的代理類:
可以看出Coder$$EnhancerByCGLIB$$8e91f654.class
繼承了Coder類(也就是說自動生成的代理類其實是被代理類的一個子類),
並且重寫了Coder類的work()方法,重寫後的work()方法會呼叫自定義的方法攔截器AttendanceMethodInterceptor裡的intercept()
方法。
然後看下Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,從名稱上可以看出這個類的前半段和上面的類的名稱
是一樣的,後半段拼接上了$$FastClassByCGLIB$$4e5eb5aa
,從功能上說,這個類是上面的代理類的索引類,重點關注下里面的
getIndex()
方法和invoke()
方法:
最後看下Coder$$FastClassByCGLIB$$398819d0
,這個類是被代理類Coder的索引類,重點也是關注下里面的
getIndex()
方法和invoke()
方法:
知道了這3個類的作用後,再一步一步看下範例程式碼中coder.work();
的呼叫過程,因為coder是生成的代理類的範例,所以
coder.work();
首先呼叫的是Coder$$EnhancerByCGLIB$$8e91f654
的work()方法:
這裡的var10000是自定義的方法攔截器AttendanceMethodInterceptor,所以執行的是紅色截圖裡的intercept()
方法,也就是:
然後看下invokeSuper()
方法:
首先執行的是init()
方法,在該方法內部對fastClassInfo欄位進行了賦值:
從上圖可以看出,fci.f1是自動生成的Coder類的索引類Coder$$FastClassByCGLIB$$398819d0
,所以fci.i1 = fci.f1.getIndex(sig1);
其實執行的是的Coder$$FastClassByCGLIB$$398819d0
的getIndex()
方法:
fci.f2是自動生成的代理類的索引類Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,
所以fci.i2 = fci.f2.getIndex(sig2);
其實執行的是的Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
的
getIndex()
方法:
看完init()
方法後再回到invokeSuper()
方法:
上圖中的FastClassInfo fci = fastClassInfo;
使用到的欄位fastClassInfo在init()
方法內部已經賦過值,
fci.f2其實是自動生成的代理類的索引類Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,
fci.i2值是1,
所以fci.f2.invoke(fci.i2, obj, args);
實際執行的是:
這裡的var10000其實是自動生成的代理類Coder$$EnhancerByCGLIB$$8e91f654
的範例,所以接著呼叫的是
代理類Coder$$EnhancerByCGLIB$$8e91f654
的CGLIB$work$0()
方法:
這裡的super指的是Coder類,所以super.work();
實際執行的是Coder類的work()方法:
綜上所述,coder.work();
的呼叫順序依次是:
代理類--->自定義方法攔截器--->代理類索引類getIndex()方法-->代理類索引類invoke()方法--->代理類--->被代理類。
關於JDK動態代理,可以檢視上一篇部落格:【深度思考】聊聊JDK動態代理原理。
瞭解了JDK動態代理和CGLIB動態代理的原理後,現在來比較下兩者的區別,這也是面試時幾乎必問的一道面試題。
使用JDK動態代理,被代理類必須要實現介面,使用CGLIB動態代理,被代理類可以不實現介面
原因分析:
JDK動態代理生成的代理類繼承了
java.lang.reflect.Proxy
,因為Java是單繼承的,如果不通過實現介面的形式,無法對類進行擴充套件。
CGLIB動態代理生成的代理類實際上是被代理類的子類,所以被代理類可以不實現介面。
自動生成類的數量不同
JDK動態代理只會生成1個代理類,一般情況下名稱為:
com.sun.proxy.$Proxy0
。CGLIB動態代理會生成好幾個類,核心的3個分別是:
1)代理類:被代理類的子類,名稱格式為
Coder$$EnhancerByCGLIB$$8e91f654
,包名和被代理類包名一致。2)代理類的索引類:名稱格式為
Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,包名和被代理類包名一致。
3)被代理類的索引類:名稱格式為
Coder$$FastClassByCGLIB$$398819d0
,包名和被代理類包名一致。
生成代理類技術不同
JDK動態代理使用JDK自帶的ProxyGenerator類生成位元組碼檔案。
CGLIB動態代理使用ASM框架生成位元組碼檔案。
呼叫方式不同
JDK動態代理:代理類--->InvocationHandler.invoke()--->被代理類方法(用到了反射)。
CGLIB動態代理:代理類--->MethodInterceptor.intercept()--->代理類索引類getIndex()--->
代理類索引類invoke()--->代理類--->被代理類。(直接呼叫)
文章持續更新,歡迎關注微信公眾號「申城異鄉人」第一時間閱讀!