歡迎光臨
每天分享高質量文章

CGLIB,Java的動態代理神器

原文出自【佔小狼的部落格】,轉載請註明出處

前言

之前講過《JDK的動態代理實現》,內部透過反射類 Proxy和 InvocationHandler回呼介面實現,要求委託類必須實現一個介面,只能對該類介面中定義的方法進行代理,這在實際的專案中有一定的侷限性,遇到沒有介面的委託類,就無法應對。

幸好,有CGLIB這個神器的存在。

CGLIB實現

使用CGLIB(Code Generation Library)實現動態代理,並不要求委託類必須實現介面,底層採用ASM位元組碼生成框架生成代理類的位元組碼,下麵透過一個例子看看如何實現動態代理。

1、定義業務邏輯

  1. public class UserServiceImpl {  

  2.    public void add() {  

  3.        System.out.println("This is add service");  

  4.    }  

  5.    public void delete(int id) {  

  6.        System.out.println("This is delete service:delete " + id );  

  7.    }  

  8. }

2、實現 MethodInterceptor介面,定義方法的攔截器

  1. public class MyMethodInterceptor implements MethodInterceptor {

  2.    public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {

  3.        System.out.println("Before:" + method);  

  4.        Object object = proxy.invokeSuper(obj, arg);

  5.        System.out.println("After:" + method);

  6.        return object;

  7.    }

  8. }

3、利用 Enhancer類生成代理類;

  1. Enhancer enhancer = new Enhancer();  

  2. enhancer.setSuperclass(UserServiceImpl.class);  

  3. enhancer.setCallback(new MyMethodInterceptor());  

  4. UserServiceImpl userService = (UserServiceImpl)enhancer.create(); 

4、 userService.add()的執行結果:

  1. Before: add

  2. This is add service

  3. After: add

代理物件的生成過程由Enhancer類實現,大概步驟如下: 

1、生成代理類Class的二進位制位元組碼; 

2、透過 Class.forName載入二進位制位元組碼,生成Class物件; 

3、透過反射機制獲取實體構造,並初始化代理類物件。

位元組碼生成

Enhancer是CGLib的位元組碼增強器,可以方便的對類進行擴充套件,內部呼叫 GeneratorStrategy.generate方法生成代理類的位元組碼,透過以下方式可以生成class檔案。

  1. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\\\Code\\\\whywhy\\\\target\\\\classes\\\\zzzzzz")

使用 反編譯工具 procyon 檢視代理類實現

  1. java -jar procyon-decompiler-0.5.30.jar UserService$$EnhancerByCGLIB$$394dddeb;

反編譯之後的代理類add方法實現如下:

  1. import net.sf.cglib.core.Signature;

  2. import net.sf.cglib.core.ReflectUtils;

  3. import net.sf.cglib.proxy.MethodProxy;

  4. import java.lang.reflect.Method;

  5. import net.sf.cglib.proxy.MethodInterceptor;

  6. import net.sf.cglib.proxy.Callback;

  7. import net.sf.cglib.proxy.Factory;

  8. //

  9. // Decompiled by Procyon v0.5.30

  10. //

  11. public class UserService$$EnhancerByCGLIB$$394dddeb extends UserService implements Factory

  12. {

  13.    private boolean CGLIB$BOUND;

  14.    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;

  15.    private static final Callback[] CGLIB$STATIC_CALLBACKS;

  16.    private MethodInterceptor CGLIB$CALLBACK_0;

  17.    private static final Method CGLIB$add$0$Method;

  18.    private static final MethodProxy CGLIB$add$0$Proxy;

  19.    private static final Object[] CGLIB$emptyArgs;

  20.    static void CGLIB$STATICHOOK2() {

  21.        CGLIB$THREAD_CALLBACKS = new ThreadLocal();

  22.        CGLIB$emptyArgs = new Object[0];

  23.        final Class> forName = Class.forName("UserService$$EnhancerByCGLIB$$394dddeb");

  24.        final Class> forName3;

  25.        CGLIB$add$0$Method = ReflectUtils.findMethods(new String[] { "add", "()V" }, (forName3 = Class.forName("UserService")).getDeclaredMethods())[0];

  26.        CGLIB$add$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "add", "CGLIB$add$0");

  27.    }

  28.    final void CGLIB$add$0() {

  29.        super.add();

  30.    }

  31.    public final void add() {

  32.        MethodInterceptor cglib$CALLBACK_2;

  33.        MethodInterceptor cglib$CALLBACK_0;

  34.        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {

  35.            CGLIB$BIND_CALLBACKS(this);

  36.            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);

  37.        }

  38.        if (cglib$CALLBACK_0 != null) {

  39.            cglib$CALLBACK_2.intercept((Object)this, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Method, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$emptyArgs, UserService$$EnhancerByCGLIB$$394dddeb.CGLIB$add$0$Proxy);

  40.            return;

  41.        }

  42.        super.add();

  43.    }

  44.    static {

  45.        CGLIB$STATICHOOK2();

  46.    }

  47. }

透過CGLIB生成的位元組碼相比JDK實現來說顯得更加複雜。

1、代理類 UserService$$EnhancerByCGLIB$$394dddeb繼承了委託類 UserSevice,且委託類的final方法不能被代理; 

2、代理類為每個委託方法都生成兩個方法,以add方法為例,一個是重寫的add方法,一個是CGLIB$add$0方法,該方法直接呼叫委託類的add方法; 

3、當執行代理物件的add方法時,會先判斷是否存在實現了MethodInterceptor介面的物件 cglib$CALLBACK_0,如果存在,則呼叫MethodInterceptor物件的 intercept方法:

  1. public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) {

  2.    System.out.println("Before:" + method);  

  3.    Object object = proxy.invokeSuper(obj, arg);

  4.    System.out.println("After:" + method);

  5.    return object;

  6. }

引數分別為:1、代理物件;2、委託類方法;3、方法引數;4、代理方法的MethodProxy物件。

4、每個被代理的方法都對應一個MethodProxy物件, methodProxy.invokeSuper方法最終呼叫委託類的add方法,實現如下:

  1. public Object invokeSuper(Object obj, Object[] args) throws Throwable {

  2.    try {

  3.        init();

  4.        FastClassInfo fci = fastClassInfo;

  5.        return fci.f2.invoke(fci.i2, obj, args);

  6.    } catch (InvocationTargetException e) {

  7.        throw e.getTargetException();

  8.    }

  9. }

單看 invokeSuper方法的實現,似乎看不出委託類add方法呼叫,在MethodProxy實現中,透過FastClassInfo維護了委託類和代理類的FastClass。

  1. private static class FastClassInfo {

  2.    FastClass f1;

  3.    FastClass f2;

  4.    int i1;

  5.    int i2;

  6. }

以add方法的methodProxy為例,f1指向委託類物件,f2指向代理類物件,i1和i2分別是方法add和CGLIB$add$0在物件中索引位置。

FastClass實現機制

FastClass其實就是對Class物件進行特殊處理,提出下標概念index,透過索引儲存方法的取用資訊,將原先的反射呼叫,轉化為方法的直接呼叫,從而體現所謂的fast,下麵透過一個例子瞭解一下FastClass的實現機制。 1、定義原類

  1. class Test {

  2.    public void f(){

  3.        System.out.println("f method");

  4.    }

  5.    public void g(){

  6.        System.out.println("g method");

  7.    }

  8. }

2、定義Fast類

  1. class FastTest {

  2.    public int getIndex(String signature){

  3.        switch(signature.hashCode()){

  4.        case 3078479:

  5.            return 1;

  6.        case 3108270:

  7.            return 2;

  8.        }

  9.        return -1;

  10.    }

  11.    public Object invoke(int index, Object o, Object[] ol){

  12.        Test t = (Test) o;

  13.        switch(index){

  14.        case 1:

  15.            t.f();

  16.            return null;

  17.        case 2:

  18.            t.g();

  19.            return null;

  20.        }

  21.        return null;

  22.    }

  23. }

在FastTest中有兩個方法, getIndex中對Test類的每個方法根據hash建立索引, invoke根據指定的索引,直接呼叫標的方法,避免了反射呼叫。所以當呼叫 methodProxy.invokeSuper方法時,實際上是呼叫代理類的 CGLIB$add$0方法, CGLIB$add$0直接呼叫了委託類的add方法。

JDK和CGLIB動態代理的區別

1、JDK動態代理生成的代理類和委託類實現了相同的介面; 

2、CGLIB動態代理中生成的位元組碼更加複雜,生成的代理類是委託類的子類,且不能處理被final關鍵字修飾的方法; 

3、JDK採用反射機制呼叫委託類的方法,CGLIB採用類似索引的方式直接呼叫委託類方法;

END

贊(0)

分享創造快樂

© 2022 知識星球   網站地圖