CGLib淺析

zc發表於2021-09-11

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檔案

1

CGLib反編譯方法

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

  • 使用 authas 配合 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)來實際真正的呼叫要執行的方法;

思考

這時候,可能心中還有一個疑惑,明明下面兩個方法中都有 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

@Override
public 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;
}

接著,執行對應方法

@Override
public 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