日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

cglib是什么

CGLIB是一個用于生成代理類的開源庫,它與JDK自帶的動態代理在一些方面有所不同,主要體現在以下幾點:

  1. 接口要求:JDK動態代理要求被代理的類實現某個接口,而CGLIB則沒有這個限制。CGLIB可以代理那些沒有實現接口的類,使得它更加靈活,可以代理更廣泛的類。
  2. 執行速度: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 代理類執行起來更快的原因。

分享到:
標簽:cglib
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定