CGLib淺析


CGLib淺析

什么是CGLib

CGLIB實現動態代理,並不要求被代理類必須實現接口,底層采用asm字節碼生成框架生成代理類字節碼(該代理類繼承了被代理類)。

所以被代理類一定不能定義為final class並且對於final 方法不能被代理。

實現需要

//MethodInterceptor接口的intercept方法
/**
*obj 代理對象
*method 委托類方法,被代理對象的方法字節碼對象
*arg 方法參數
*MethodProxy 代理方法MethodProxy對象,每個方法都會對應有這樣一個對象 
*/
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy)
Ehancer enhancer = new Enhancer() //Enhancer為字節碼增強器,很方便對類進行擴展
enhancer.setSuperClass(被代理類.class);
enhancer.setCallback(實現MethodInterceptor接口的對象)
enhancer.create()

代碼案例

導入依賴

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

UserDaoImpl 用戶實現類(RealSubject)

public class UserDaoImpl {
    public boolean insert(String name) {
        System.out.println("insert name=" + name);
        return true;
    }

    public final boolean insert1(String name) {
        System.out.println("final insert name=" + name);
        return true;
    }
}

CglibProxy CGLIB代理類(Proxy)

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("----------before----------");
        System.out.println("Proxy=" + o.getClass());
        System.out.println("method=" + method);
        System.out.println("args=" + Arrays.toString(objects));
        System.out.println("methodProxy=" + methodProxy);
        //執行目標方法對象
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("----------after----------");
        return result;
    }
}

ProxyFactory 代理工廠

public class ProxyFactory {
    private static Enhancer enhancer = new Enhancer();
    private static CglibProxy cglibProxy = new CglibProxy();
    
    public static Object getProxy(Class cls) {
        enhancer.setSuperclass(cls);
        enhancer.setCallback(cglibProxy);
        return enhancer.create();
    }
    
    public static void main(String[] args) {
        UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
        userDao.insert("zc");
    }
}
5

CGLib流程

Ehancer enhancer = new Enhancer() //Enhancer為字節碼增強器,很方便對類進行擴展
enhancer.setSuperClass(被代理類.class);    //為生成的類設置父類
enhancer.setCallback(實現MethodInterceptor接口的對象);
enhancer.create();    //創建代理對象

創建代理對象會經過三步:

1.生成代理類的二進制字節碼文件。

2.加載二進制字節碼文件到JVM,生成class對象。

3.反射獲得實例構造方法,創建代理對象。

接下來,看看反編譯出現的Java文件

2

CGLib反編譯方法

  • 使用以下語句
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\xxxx")
  • 使用HSDB進行反編譯

  • 使用 arthas 配合 jad進行反編譯

具體使用方法可以自行查找


我們以insert() 為入口開始:

UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);  //Ehancer,創建代理對象
userDao.insert("zc");

這時候會進入UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2 中的 insert()

public final boolean insert(String string) {
    MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
    if (methodInterceptor == null) {
        UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);
        methodInterceptor = this.CGLIB$CALLBACK_0;
    }
    if (methodInterceptor != null) {
        Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);
        return object == null ? false : (Boolean)object;
    }
    return super.insert(string);
}

其實在上述方法中,是因為設置了 enhancer.setCallback(cglibProxy); ,只要不為空,則會執行

Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);

this : 當前代理對象

CGLIB$say$0$Method : 目標類中的方法

CGLIB$emptyArgs : 方法參數,這里為空

CGLIB$say$0$Proxy : 代理類生成的代理方法

這樣會去調用 CglibProxy.intercept() 方法

/**
 * Object:cglib生成的代理對象
 * Method:被代理對象方法
 * Object[]:方法入參
 * MethodProxy:代理的方法
 */
public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("----------before----------");
        
        //執行目標方法對象
        Object result = methodProxy.invokeSuper(o, objects);

        System.out.println("----------after----------");
        return result;
    }
}

這時候進入 methodProxy.invokeSuper(o, objects) 方法

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

第一反應,可能不知道是f2i2 都是什么,這里扯一下 init() 方法,其中對於FastClass 類,就是反編譯出來的對應類:

private void init()
{
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;
                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);    // 被代理類FastClass
                fci.f2 = helper(ci, ci.c2);    // 代理類FastClass
                fci.i1 = fci.f1.getIndex(sig1); // 被代理類的方法簽名(index)
                fci.i2 = fci.f2.getIndex(sig2); // 代理類的方法簽名(index)
                fastClassInfo = fci;
                createInfo = null;
            }
        }
    }
}

private static class FastClassInfo
{
    FastClass f1;  // 被代理類FastClass
    FastClass f2;  // 代理類FastClass
    int i1;  // 被代理類的方法簽名(index)
    int i2;  // 代理類的方法簽名(index)
}
fci.f2 = helper(ci, ci.c2);    // 代理類FastClass
fci.i2 = fci.f2.getIndex(sig2); // 代理類的方法簽名(index)

這時候看到UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5

@Override
public int getIndex(Signature signature) {
    String string = ((Object)signature).toString();
    switch (string.hashCode()) {
         //XXXXX 省略
            
        case -747055045: {
            if (!string.equals("CGLIB$insert$0(Ljava/lang/String;)Z")) break;
            return 16;
        }
            
       //XXXXX 省略
    return -1;
}

所以 i2 在其中為 16 , 這時候運行下面方法

fci.f2.invoke(fci.i2, obj, args)

即,UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5invoke() 方法

@Override
public Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {
    UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2 f32f6ae22 = (UserDaoImpl$$EnhancerByCGLIB$.f32f6ae2)object;
    try {
        switch (n) {
             //XXXXX 省略
            case 16: {
                return new Boolean(f32f6ae22.CGLIB$insert$0((String)objectArray[0]));
            }
            //XXXXX 省略
        }
    }
    catch (Throwable throwable) {
        throw new InvocationTargetException(throwable);
    }
    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

可以看到,他進行調用的是 UserDaoImpl$$EnhancerByCGLIB$f32f6ae2 中的 CGLIB$insert$0() 方法

final boolean CGLIB$insert$0(String string) {
    return super.insert(string);
}

這里,才是真正調用到了父類(目標類)中對應的方法。至此,整個的調用流程完畢。

流程總結

  1. 首先生成代理對象。創建增強類enhancer,設置代理類的父類,設置回調攔截方法,返回創建的代理對象;

  2. 調用代理類中的方法。這里調用的代理類中的方法實際上是重寫的父類的攔截。重寫的方法中會去調用intercept方法;

  3. 調用intercept,方法中會對調用代理方法中的invokeSuper方法。而在 invokeSuper 中維護了一個 FastClassInfo類,其包含四個屬性字段,分別為FastClass f1(目標類)FastClass f2 (代理類)int i1(目標類要執行方法的下標)int i2(代理類要執行方法的下標); invokeSuper中會調用的為代理類中的對應方法(代理類繼承於父類的時候,對於其父類的方法,自己會生成兩個方法,一個是重寫的方法,一個是代理生成的方法,這里調用的即是代理生成的方法);

  4. 調用代理類中的代理方法。代理方法中通過super.xxxx(string)來實際真正的調用要執行的方法;

思考

JDK動態代理CGLib動態代理 有什么本質區別?

首先我們可以回想一下JDK動態代理CGLib動態代理,兩者代理類中的區別:

//CGLib
private static Enhancer enhancer = new Enhancer();
private static CglibProxy cglibProxy = new CglibProxy();

public static Object getProxy(Class cls) {
  enhancer.setSuperclass(cls);
  enhancer.setCallback(cglibProxy);
  return enhancer.create();
}

public static void main(String[] args) throws InterruptedException {
  UserDaoImpl userDao = (UserDaoImpl) getProxy(UserDaoImpl.class);
  userDao.insert("zc");
}
//JDK
public static Object getProxy(Object proxyObj) {
  return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                                proxyObj.getClass().getInterfaces(), new MyInvocationHandler(proxyObj));
}

public static void main(String[] args) {
  AgeDao ageDao = (AgeDao) getProxy(new NameAndAgeDaoImpl());
  ageDao.addAge(20);
}

我理解,這2種動態代理,最本質的區別就是:JDK動態代理是基於委托思想,而CGLib動態代理是基於繼承的思想。

基於委托思想,JDK生成動態代理類的時候,需要傳入被代理類(被委托類)的對象,可以看作是對象級別的重用機制

基於繼承思想,動態代理類繼承了被代理類,理論上父類的所有開放方法對於子類都是可見的,可以看作是級別的重用機制;

而怎么理解上面的話呢,這就要回歸到動態代理的本質:

動態代理 = 攔截器機制 + 回溯到被代理類的能力

  • 對於JDK動態代理:

JDK動態代理 = 攔截器機制(InvocationHandler) + 回溯到被代理類的能力(反射調用被代理類對象相關方法)

JDK動態代理中,生成的代理類的全限定類名是com.sun.proxy.$ProxyN(N是正整數,比如$Proxy0),它繼承了com.sun.proxy類,該類中存在一個InvocationHandler類型的h成員變量,它就是攔截器。但這里會存在一個問題,由於我們希望代理類和被代理類在行為上是一致的(具有相同的類型),所以JDK動態代理需要引入接口的概念,代理對象和被代理對象需要具有相同的接口定義。

所以,在我們使用JDK動態代理的過程中,我們需要自定義攔截器,實現InvocationHandler 接口,然后將被代理對象(被委托對象)注入到攔截器中。當調用接口方法時,會首先調用攔截器的invoke方法,攔截器invoke方法內部,會經過反射去調用被代理對象的相應方法。

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //執行目標方法對象
        Object result = method.invoke(target, args);
        return result;
    }
}
  • 對於CGLIB動態代理:

CGLIB動態代理 = 攔截器機制(MethodInterceptor) + 回溯到被代理類的能力 (FastClass輔助類、MethodProxy類)

CGLIB動態代理中,生成的代理類的全限定類名是很自由的。因為它是基於繼承機制,代理類繼承了被代理類。 在生成的代理類中,會存在一個MethodInterceptor類型的CGLIB$CALLBACK_0成員變量,它就是攔截器。由於是繼承,代理類天然就可以調用到父類(被代理類)的方法,因此這里不再需要注入被代理類的對象實例了。但這里仍然存在一個很核心的問題:代理類看起來,既要能夠調用到攔截器,又要可以回溯到父類(被代理類)的原始方法,這看起來很矛盾。怎么解決呢?

其實很簡單,CGLIB生成的代理,對於被代理類的原有方法(比如上面的insert方法),會調用到攔截器。而與此同時,CGLIB還增加了隱藏的能夠回溯到原始方法的傳送門方法(比如CGLIB$insert$0),這樣就可以兩全其美了。

可是問題又來了,攔截器是我們自己來實現並添加業務自定義邏輯的,當我們想要在攔截器里調用到原始的被代理對象的insert方法,該如何去實現呢?

一種可行的方式是使用反射,調用代理對象的隱藏傳送門CGLIB$insert$0方法。不得不說,這的確是可行的,但成本也非常大,你需要提前反編譯動態代理的源碼,找到對應的方法名。而動態代理之所以被稱為動態代理,核心在於它是在jvm運行期動態生成的,所以這並不符合我們的初衷。而且要讓攔截器對所有的方法進行適配,這顯然也不現實。

我們說,沒有什么問題不能通過加一層解決,CGLIB又一次證明了它的正確性。為了解決這個問題,CGLIB框架引入了MethodProxy的概念。針對每一個被代理對象的方法(比如insert),都有一個MethodProxy,它對外提供了invokeSuperinvoke方法,分別可以路由到CGLIB$insert$0insert方法。

所以,我們在攔截器做攔截操作時,直接調用對應MethodProxyinvokeSuper就可以路由到代理對象的隱藏傳送門方法啦。

public class CglibProxy implements MethodInterceptor {   
    @Override   
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {    
        //執行目標方法對象       
        Object result = methodProxy.invokeSuper(o, objects);      
        return result;   
    }
}
  • 兩者區別
  1. JDK動態代理基於接口實現,必須先定義接口

    CGLib動態代理基於被代理類實現,可以直接定義或者實現接口的類

  2. JDK動態代理需要實現InvocationHanlder接口,加上反射機制實現代理類

    CGLib動態代理需要實現MethodInterceptor接口,對於代理類不可使用final修飾

  3. JDK動態代理是委托機制,委托hanlder,生成新的委托類,調用實現類方法;

    CGLib動態代理則使用繼承機制,被代理類和代理類是繼承關系,直接調用其中方法;

在其中FastClass類發揮了什么作用呢?為什么要有FastClass類

我在上面稱FastClass類為輔助類:

  • 首先在invoke()invokeSuper()中都存在FastClass
public Object invoke(Object obj, Object[] args) throws Throwable {	
    //初始化 
    return fci.f1.invoke(fci.i1, obj, args);	
    //XXXXX 省略
}

public Object invokeSuper(Object obj, Object[] args) throws Throwable { 
    //初始化  
    return fci.f2.invoke(fci.i2, obj, args);	
    //XXXXX 省略
}

private static class FastClassInfo{  
    FastClass f1; // UserDaoImpl$$FastClassByCGLIB$$481edb7a  
    FastClass f2; // UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2$$FastClassByCGLIB$$9fc87de5  
    int i1;  
    int i2;
}

在上面的舉例中可以看到,在生成的代理類、被代理類的FastClass類中,有 getindex()、invoke()等,獲取索引,通過索引調用方法等。所以我稱為 FastClass輔助類

在JDK動態代理中,調用目標對象的方法使用的是反射,而在CGLIB動態代理中使用的是FastClass機制

  1. FastClass使用:動態生成一個繼承FastClass的類,並向類中寫入委托對象,直接調用委托對象的方法。
  2. FastClass邏輯:在繼承FastClass的動態類中,根據方法簽名(方法名字+方法參數)得到方法索引,根據方法索引調用目標對象方法。
  3. FastClass優點:FastClass用於代替Java反射,避免了反射造成調用慢的問題。

明明下面兩個方法中都有 super.xxxx(string) , 但是使用的是 invokeSuper() ,而不是 invoke()

看下這兩個方法:

final boolean CGLIB$insert$0(String string) {   
    return super.insert(string);
}
public final boolean insert(String string) {   
    MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;   
    if (methodInterceptor == null) {       
        UserDaoImpl$$EnhancerByCGLIB$$f32f6ae2.CGLIB$BIND_CALLBACKS(this);      
        methodInterceptor = this.CGLIB$CALLBACK_0;   
    }   
    if (methodInterceptor != null) {       
        Object object = methodInterceptor.intercept(this, CGLIB$insert$0$Method, new Object[]{string}, CGLIB$insert$0$Proxy);   
        return object == null ? false : (Boolean)object;   
    }    
    return super.insert(string);
}
  • 如果使用invokeSuper()
public Object invokeSuper(Object obj, Object[] args) throws Throwable {        
    try {           
        init();           
        FastClassInfo fci = fastClassInfo;            
        //執行被代理類FastClass 的對應 i2 索引的方法            
        return fci.f2.invoke(fci.i2, obj, args);        
    } catch (InvocationTargetException e) {           
        throw e.getTargetException();       
    }    
}

就是按照上面講的步驟,先進行 insert() 方法,經過intercept,最終可以運行到 CGLIB$insert$0() ,調用到了父類(目標類)中對應的方法。

  • 如果使用invoke()
public Object invoke(Object obj, Object[] args) throws Throwable {    
    try {       
        init();      
        FastClassInfo fci = fastClassInfo;       
        //執行代理類FastClass 的對應 i1 索引的方法       
        return fci.f1.invoke(fci.i1, obj, args);    
    } catch (InvocationTargetException e) {       
        throw e.getTargetException();   
    } catch (IllegalArgumentException e) {        
        if (fastClassInfo.i1 < 0)           
            throw new IllegalArgumentException("Protected method: " + sig1);       
        throw e;   
    }
}

i1 的值通過下面方法獲取為 1

@Overridepublic int getIndex(Signature signature) {   
    String string = ((Object)signature).toString();    
    switch (string.hashCode()) {         
            //XXXXX 省略       
        case -982250262: {           
            if (!string.equals("insert(Ljava/lang/String;)Z"))
                break;           
            return 1;       
        }            
            //XXXXX 省略   
    }    
    return -1;
}

接着,執行對應方法

@Overridepublic Object invoke(int n, Object object, Object[] objectArray) throws InvocationTargetException {   
    UserDaoImpl userDaoImpl = (UserDaoImpl)object;   
    try {       
        switch (n) {         
                //XXXXX 省略         
            case 1: {             
                return new Boolean(userDaoImpl.insert((String)objectArray[0]));         
            }         
                //XXXXX 省略       
        }    
    }    
    catch (Throwable throwable) {        
        throw new InvocationTargetException(throwable);    
    }   
    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

先進行 insert() 方法,經過intercept,通過 invoke() 方法,再次進入insert()方法,繼而是一直死循環。

個人博客為:
MoYu's HomePage


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM