cglib是什么
CGLIB是一個用于生成代理類的開源庫,它與JDK自帶的動態代理在一些方面有所不同,主要體現在以下幾點:
- 接口要求:JDK動態代理要求被代理的類實現某個接口,而CGLIB則沒有這個限制。CGLIB可以代理那些沒有實現接口的類,使得它更加靈活,可以代理更廣泛的類。
- 執行速度:CGLIB在目標方法的執行速度上更快。這是因為CGLIB采用了一種稱為FastClass的機制,它通過生成的代理類直接調用目標方法,避免了一些額外的方法調用,從而提高了執行效率。相比之下,JDK動態代理需要通過反射調用目標方法,會引入一些性能開銷。
需要注意的是,CGLIB通過生成繼承自目標類的子類來實現代理,因此目標類和目標方法不能被聲明為final,否則CGLIB無法生成代理類。

常見的動態代理
在代理類庫中,我們經常接觸到的是JDK動態代理和前文提到的CGLIB。這兩個類庫都能在運行時生成代理類,并被廣泛應用于Spring的AOP(面向切面編程)中。
除了JDK動態代理和CGLIB之外,還存在其他一些第三方類庫,如JAVAssist和AspectJ,它們不僅可以在運行時生成代理類,還可以在編譯時生成代理類。這些類庫提供了更多的靈活性和功能,但在本文中不作深入討論。
Spring框架的AOP模塊在實現上使用了JDK動態代理和CGLIB兩種技術。對于實現了接口的類,Spring默認使用JDK動態代理來生成代理類;而對于沒有實現接口的類,Spring會使用CGLIB來生成代理類。這樣,Spring能夠根據目標類的特性選擇最合適的代理方式。
為什么要用動態代理
為了更好地解答這個問題,這里通過一個簡單的例子來逐步說明。
首先,我有一個用戶相關的Controller。
class RoleController {
public ResponseBean create(RoleCreateDTO dto){
String id = roleService.create(dto);
return Response.of(id);
}
public ResponseBean update(RoleUpdateDTO dto){
String id = roleService.update(dto);
return Response.of(id);
}
public ResponseBean delete(RoleDeleteDTO dto){
String id = roleService.delete(dto);
return Response.of(id);
}
public ResponseBean getById(String id){
RoleVO role = roleService.getById(id);
return Response.of(role);
}
}
為了方便監控跟蹤,我希望將每個方法的入參、出參、當前登錄人等等信息打印出來。簡單的做法就是直接在每個方法里嵌入打印日志的代碼,如下:
class RoleController {
public ResponseBean create(RoleCreateDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.create(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean update(RoleUpdateDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.update(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean delete(RoleDeleteDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.delete(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean getById(String id){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.getById(id));
// 打印出參日志
// ······
return response;
}
}
明顯可以看出來,這種做法有兩個的問題:一是需要添加大量重復代碼,二是代碼耦合度高。
當然,問題要一個個解決,首先,針對第二個問題,我創建了一個
RoleControllerCommonLogProxy來專門處理請求日志,如下:
class RoleControllerCommonLogProxy extends RoleController {
public ResponseBean create(RoleCreateDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.create(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean update(RoleUpdateDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.update(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean delete(RoleDeleteDTO dto){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.delete(dto));
// 打印出參日志
// ······
return response;
}
public ResponseBean getById(String id){
// 打印入參日志
// ······
ResponseBean response = Response.of(roleService.getById(id));
// 打印出參日志
// ······
return response;
}
}
在上面的例子中,我們通過間接訪問RoleController的方式,即通過
RoleControllerCommonLogProxy來進行訪問。這種方式其實就是代理,更準確地說是靜態代理,與接下來要介紹的動態代理有所不同。
靜態代理解決了代碼耦合的問題,但也帶來了新的挑戰:需要手動創建和維護大量的代理類。每個控制器都需要增加一個代理類,這意味著在項目中會有許多*Proxy類存在。此外,當RoleController添加新的方法時,還需要在相應的代理類中進行實現。
在這種情況下,我們會想,如果代理類能夠自動生成就太好了。于是,動態代理就應運而生。
使用動態代理,我們只需定義代理類的邏輯,它就能幫助我們生成相應的代理類(無論是在編譯時還是運行時生成),而不需要手動創建。這樣一來,我們可以更簡便地實現面向切面編程(AOP)。
我們使用動態代理的根本目的是為了更加方便地實現AOP。通過動態代理,我們能夠自動生成代理類,避免手動創建大量的代理類,并且能夠靈活應對業務代碼的變化。
自定義代理類的邏輯
首先,我們需要在某個地方定義代理類的邏輯。在本文的例子中,代理邏輯是在方法執行前打印入參,方法執行后打印出參。為了實現這個邏輯,我們可以實現MethodInterceptor接口。根據AOP聯盟的標準,MethodInterceptor屬于一種通知(Advice)。
需要注意的是,在使用CGLIB時,我們應該使用proxy.invokeSuper來調用目標類的方法,而不是使用method.invoke。這是為了避免出現棧溢出等問題。如果堅持使用method.invoke,我們需要將目標類對象作為LogInterceptor的成員屬性,并在調用method.invoke時將其作為參數傳遞,而不是使用
MethodInterceptor.intercept方法的參數obj。然而,我并不推薦這樣做,因為這樣做將無法充分利用CGLIB代理類的高性能特性(然而,仍有很多人這樣做)。
public class LogInterceptor implements MethodInterceptor {
// 這里傳入的obj是代理類對象,而不是目標類對象
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.err.println("打印" + method.getName() + "方法的入參");
// 注意,這里要調用proxy.invokeSuper,而不是method.invoke,不然會出現棧溢出等問題
Object obj2 = proxy.invokeSuper(obj, args);
System.err.println("打印" + method.getName() + "方法的出參");
return obj2;
}
}
獲取代理類
我們主要通過Enhancer來配置、獲取代理類對象,下面的代碼挺好理解的,我們需要告訴 cglib,我要代理誰,代理的邏輯放在哪里。
@Test
public void testBase() throws InterruptedException {
// 設置輸出代理類到指定路徑,便于后面分析
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/tracy/test");
// 創建Enhancer對象
Enhancer enhancer = new Enhancer();
// 設置哪個類需要代理
enhancer.setSuperclass(RoleController.class);
// 設置怎么代理
enhancer.setCallback(new LogInterceptor());
// 獲取代理類實例
RoleController roleController = (RoleController) enhancer.create();
// 測試代理類
System.out.println("-------------");
roleController.save();
System.out.println("-------------");
roleController.delete();
System.out.println("-------------");
roleController.update();
System.out.println("-------------");
roleController.find();
}
此外,我們還可以同時設置多個Callback。值得注意的是,設置多個Callback并不意味著一個方法可以被多個Callback攔截,而是表示目標類中的不同方法可以被不同的Callback攔截。因此,當設置了多個Callback時,CGLIB需要知道哪些方法應使用哪個Callback。為此,我們需要額外設置一個CallbackFilter來指定每個方法應使用的具體Callback。
通過設置多個Callback并配合CallbackFilter,我們可以更加靈活地對目標類的不同方法進行攔截和處理。每個Callback可以實現不同的邏輯,從而實現對不同方法的個性化處理。
需要注意的是,CallbackFilter的返回值是一個整數,用于表示使用哪個Callback。通常,我們會根據方法的名稱、參數類型等信息進行判斷,并返回對應的Callback索引。
(此處已添加書籍卡片,請到今日頭條客戶端查看)代理類的源碼
下面看看代理類文件的源碼(這里僅展示 update 方法)。
//生成類的名字規則是:被代理classname + "$$"+classgeneratorname+"ByCGLIB"+"$$"+key的hashcode
public class RoleController$$EnhancerByCGLIB$$e6f193aa extends RoleController implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
//我們一開始傳入的MethodInterceptor對象 zzs001
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
//目標類的update方法對象
private static final Method CGLIB$update$0$Method;
//代理類的update方法對象
private static final MethodProxy CGLIB$update$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
final Class<?> forName = Class.forName("cn.tracy.cglib.RoleController$$EnhancerByCGLIB$$e6f193aa");
final Class<?> forName2;
final Method[] methods = ReflectUtils.findMethods(new String[]{"update", "()V", "find", "()V", "delete", "()V", "save", "()V"},
(forName2 = Class.forName("cn.tracy.cglib.RoleController")).getDeclaredMethods());
// 初始化目標類的update方法對象
CGLIB$update$0$Method = methods[0];
// 初始化代理類update方法對象
CGLIB$update$0$Proxy = MethodProxy.create((Class) forName2, (Class) forName, "()V", "update", "CGLIB$update$0");
}
// 這個方法將直接調用RoleController的update方法
final void CGLIB$update$0() {
super.update();
}
public final void update() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
//一般走這里,即調用我們傳入MethodInterceptor對象的intercept方法
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object) this, RoleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Method, RoleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$emptyArgs, RoleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0$Proxy);
return;
}
super.update();
}
}
創建FastClass文件
在MethodProxy.invokeSuper(Object, Object[])方法中,我們會發現,兩個FastClass文件是在init方法中生成的。當然,它們也只會創建一次。
我們用到的主要是代理類的FastClass,通過它,我們可以直接調用到CGLIB$update$0方法,相當于可以直接調用目標類的update方法。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
//初始化,創建了兩個FastClass類對象
init();
FastClassInfo fci = fastClassInfo;
// 這里將直接調用代理類的CGLIB$update$0方法,而不是通過反射調用
// fci.f2:代理類的FastClass對象,fci.i2為CGLIB$update$0方法對應的索引,obj為當前的代理類對象,args為update方法的參數列表
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
private void init(){
if (fastClassInfo == null){
synchronized (initLock){
if (fastClassInfo == null){
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
// 創建目標類的FastClass對象
fci.f1 = helper(ci, ci.c1);
// 創建代理類的FastClass對象
fci.f2 = helper(ci, ci.c2);
// 獲取update方法的索引
fci.i1 = fci.f1.getIndex(sig1);
// 獲取CGLIB$update$0方法的索引,這個很重要
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
FastClass的作用
打開代理類的FastClass文件,可以看到,通過方法索引我們可以匹配到CGLIB$update$0方法,并且直接調用它,而不需要像 JDK 動態代理一樣通過反射的方式調用,極大提高了執行效率。
//傳入參數:
//n:方法索引
//o:代理類實例
//array:方法輸入參數
public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
final RoleController$$EnhancerByCGLIB$$e6f193aa roleController$$EnhancerByCGLIB$$e6f193aa = (RoleController$$EnhancerByCGLIB$$e6f193aa)o;
try {
switch (n) {
case 0: {
return new Boolean(roleController$$EnhancerByCGLIB$$e6f193aa.equals(array[0]));
}
case 1: {
return roleController$$EnhancerByCGLIB$$e6f193aa.toString();
}
case 2: {
return new Integer(roleController$$EnhancerByCGLIB$$e6f193aa.hashCode());
}
case 3: {
return roleController$$EnhancerByCGLIB$$e6f193aa.clone();
}
// ·······
case 24: {
// 通過匹配方法索引,直接調用該方法,這個方法里將直接調用目標類的方法
roleController$$EnhancerByCGLIB$$e6f193aa.CGLIB$update$0();
return null;
}
// ·······
}
catch (Throwable t) {
throw new InvocationTargetException(t);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
通過上面的分析,我們找到了 cglib 代理類執行起來更快的原因。