徹底理解動(dòng)態(tài)代理

本文篇幅比較長(zhǎng),在確定您是否需要仔細(xì)閱讀本文前,可以先思考一下下面幾個(gè)問題:

  1. 動(dòng)態(tài)代理是什么?
  2. 如何實(shí)現(xiàn)動(dòng)態(tài)代理?
  3. 所有類都能實(shí)現(xiàn)動(dòng)態(tài)代理嗎?
  4. 非目標(biāo)方法是否會(huì)被代理?
  5. 為什么 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理必須要求被代理類實(shí)現(xiàn)接口?
  6. 為什么 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理要求被代理類為非 final類?

為了理解動(dòng)態(tài)代理,我們需要先了解代理模式是怎么回事。

代理模式

代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。可以將代理模式理解為生活中常見的中介,UML 圖如下。

proxy.png
public interface Subject {
    public void doOperation();
}
public class RealSubject implements Subject {
    @Override
    public void doOperation() {
        System.out.println("RealSubject doOperation...");
    }
}
public class Proxy implements Subject {
    private Subject subject;
    public Proxy(Subject subject) {
        this.subject = subject;
    }
    @Override
    public void doOperation() {
        System.out.println("Proxy before RealSubject doOperation...");
        subject.doOperation();
        System.out.println("Proxy after RealSubject doOperation...");
    }
}
public class Client {
    public static void main(String[] args) {
        Subject subject = new Proxy(new RealSubject());
        subject.doOperation();
    }
}

上述代碼輸出如下:

Proxy before RealSubject doOperation...
RealSubject doOperation...
Proxy after RealSubject doOperation...

通過代理模式,我們可以做到在不修改目標(biāo)對(duì)象的前提下,對(duì)目標(biāo)對(duì)象進(jìn)行功能擴(kuò)展。但是上述靜態(tài)代理的不足之處在于需要事先寫好相應(yīng)的代理類,而且在接口發(fā)生變化時(shí)需要對(duì)被代理類及代理類進(jìn)行修改,對(duì)此 Java 引入了動(dòng)態(tài)代理的概念。

動(dòng)態(tài)代理

與靜態(tài)代理需要事先構(gòu)建不同,動(dòng)態(tài)代理是動(dòng)態(tài)地在內(nèi)存中生成的。一般而言,動(dòng)態(tài)代理可以由 JDK 動(dòng)態(tài)代理及CGLib 動(dòng)態(tài)代理實(shí)現(xiàn)。

JDK動(dòng)態(tài)代理

使用JDK動(dòng)態(tài)代理只需要3步即可完成:

  1. 創(chuàng)建被代理的對(duì)象 RealSubject
  2. 創(chuàng)建被代理對(duì)象的處理對(duì)象,持有目標(biāo)(被代理)對(duì)象 JDKInvocationHandler
  3. 使用Proxy的靜態(tài)方法 newProxyInstance 創(chuàng)建代理對(duì)象
public class JDKInvocationHandler implements java.lang.reflect.InvocationHandler {
    private Subject subject;
    public JDKInvocationHandler(Subject subject) {
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDKProxy before RealSubject doOperation...");
        Object ret = method.invoke(subject, args);
        System.out.println("JDKProxy after RealSubject doOperation...");
        return ret;
    }
}
public class Client {
    public static void main(String[] args) {
        //創(chuàng)建被代理的對(duì)象 realSubject
        RealSubject realSubject = new RealSubject();
        //創(chuàng)建被代理對(duì)象的處理對(duì)象
        InvocationHandler handler = new JDKInvocationHandler(realSubject);
        //創(chuàng)建代理對(duì)象
        Subject proxy = (Subject) Proxy.newProxyInstance(
            RealSubject.class.getClassLoader(),
            RealSubject.class.getInterfaces(),
            handler);
        //執(zhí)行相應(yīng)的方法
        proxy.doOperation();
    }
}

上述代碼輸出如下:

JDKProxy before RealSubject doOperation...
RealSubject doOperation...
JDKProxy after RealSubject doOperation...

其中,InvocationHandler 是 Java 自帶的接口,其定義如下:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

Proxy 靜態(tài)方法的定義如下:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

其中,

  1. loader 為類加載器,出于安全性,要求 loader 對(duì) interfaces 可見,通常使用被代理類的ClassLoader。
  2. interfaces 為被代理對(duì)象需要實(shí)現(xiàn)的所有接口。
  3. h為方法調(diào)用的實(shí)際處理者,通過 InvocationHandler 對(duì)被代理類進(jìn)行拓展。

(ps:類加載器后面會(huì)有專門介紹。)

但是,等一下,為什么需要這三個(gè)參數(shù)呢?是不是任意一個(gè) Java 類都可以動(dòng)態(tài)代理呢?我們不妨深入看看 Proxy類到底做了什么?Proxy.newProxyInstance 方法的主要內(nèi)容(刪除了安全檢測(cè)等內(nèi)容)如下:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
                   InvocationHandler h) throws IllegalArgumentException{
    final Class<?>[] intfs = interfaces.clone();
    //根據(jù)ClassLoader及接口獲取指定的代理類的Class信息
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        //在Proxy中 constructorParams被硬編碼為{InvocationHandler.class};
        //獲取代理類的參數(shù)為 InvocationHandler 的構(gòu)造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //生成代理類
        return cons.newInstance(new Object[]{h});
    } catch (XXException e) {
        throw new XXException(e.toString(), e);
    }
}
//從proxyClassCache中獲取代理類的Class信息,如果沒有則根據(jù)classLoader、ingerfaces生成加載并緩存
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
        return proxyClassCache.get(loader, interfaces);
}

動(dòng)態(tài)編譯生成代理類的代碼如下:

//通過反射動(dòng)態(tài)生成、編譯代理類,得到代理類的字節(jié)碼數(shù)據(jù)
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                                    proxyName,interfaces, accessFlags);
try {
    //動(dòng)態(tài)加載代理類
    return defineClass0(loader,proxyName,proxyClassFile,0,proxyClassFile.length);
} catch (ClassFormatError e) {
    throw new IllegalArgumentException(e.toString());
}

至此,我們知道了 Proxy 的運(yùn)行邏輯,為了進(jìn)一步了解動(dòng)態(tài)生成的代理類的內(nèi)容,我們不妨輸出并使用反編譯工具查看動(dòng)態(tài)生成的代理類的信息:

通過在程序運(yùn)行時(shí)設(shè)置:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

即可將生成的代理類保存在項(xiàng)目根目錄下,路徑為:com/sun/proxy/$ProxyNum.class

使用Java Decompiler工具查看,結(jié)果如下:

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

    @Override
    public final boolean equals(Object paramObject) {
        try {
            return ((Boolean) this.h.invoke(this, m1, 
                            new Object[] { paramObject })).booleanValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    @Override
    public final String toString() {
        try {
            return (String) this.h.invoke(this, m2, null);
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    @Override
    public final void doOperation() {
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    @Override
    public final int hashCode() {
        try {
            return ((Integer) this.h.invoke(this, m0, null)).intValue();
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals",
                    new Class[] { Class.forName("java.lang.Object") });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.iqts.proxy.Subject").getMethod("doOperation", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

到這里可以大概推測(cè)出,JDK 動(dòng)態(tài)代理是通過繼承 Proxy 類,實(shí)現(xiàn)被代理類的所有接口生成動(dòng)態(tài)代理類。這也解釋了采用 JDK 動(dòng)態(tài)代理時(shí)為什么只能使用接口引用指向代理,而不能使用被代理的具體類引用指向代理。

Subject proxy = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),
                RealSubject.class.getInterfaces(), handler);//ok

//java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to RealSubject
RealSubject proxy = (RealSubject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                RealSubject.class.getInterfaces(), handler);

此外,除了實(shí)現(xiàn)了被代理所有接口中的方法外,JDK 動(dòng)態(tài)代理還重寫了 Object 類中的 hashCode、equals、toString 三個(gè)方法。

為了更好地看出類之間的依賴關(guān)系,上述代碼可以簡(jiǎn)化如下:

public interface Subject {
    public void doOperation();
}
public class RealSubject implements Subject {
    @Override
    public void doOperation() {
        System.out.println("RealSubject doOperation...");
    }
}
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){
        Class<?> cl = getProxyClass0(loader, intfs);
        final Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
        return cons.newInstance(new Object[]{h});
    }
}
public final class $Proxy0 extends Proxy implements Subject {
    private static Method m3;
    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }
    @Override
    public final void doOperation() {
        try {
            this.h.invoke(this, m3, null);
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
}

到這里我們就不難理解,為什么 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理必須要求被代理類實(shí)現(xiàn)接口,這是由于動(dòng)態(tài)代理動(dòng)態(tài)生成的代理類需要繼承 Proxy 類,而 Java 中只能單繼承的限制使得被代理類必須實(shí)現(xiàn)接口才能實(shí)現(xiàn)動(dòng)態(tài)代理。

JDK 能夠很好地實(shí)現(xiàn)動(dòng)態(tài)代理,但是如果被代理的類沒有實(shí)現(xiàn)接口就無法實(shí)現(xiàn)動(dòng)態(tài)代理,這時(shí)候我們就需要使用第三方工具來幫忙了。

CGLib

CGLib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它可以在運(yùn)行期擴(kuò)展 Java 類及實(shí)現(xiàn)Java接口、提供方法的攔截,因此被眾多 AOP 框架使用。CGLib 包的底層是通過使用字節(jié)碼處理框架 ASM 來轉(zhuǎn)換字節(jié)碼并生成新的類。

使用 CGLib 實(shí)現(xiàn)動(dòng)態(tài)代理也很簡(jiǎn)單,首先

  1. 創(chuàng)建Enhancer對(duì)象

  2. 設(shè)置被代理類

  3. 回調(diào)對(duì)象(回調(diào)類實(shí)現(xiàn) MethodInterceptor或InvocationHandler接口)

  4. 創(chuàng)建并設(shè)置回調(diào)對(duì)象

  5. 創(chuàng)建代理對(duì)象

public class CGLib {
    public static void main(String[] args) {
        // 創(chuàng)建Enhancer對(duì)象
        Enhancer enhancer = new Enhancer();
        // 設(shè)置被代理類
        enhancer.setSuperclass(ConcreteSubject.class);
        
        // 創(chuàng)建回調(diào)對(duì)象
        //實(shí)現(xiàn) MethodInterceptor 接口
        Callback callback = new CGLibMethodInterceptor();
        //實(shí)現(xiàn) InvocationHandler 接口
        Callback callback = new CGLibInvocationHandler(new ConcreteSubject());
        
        // 設(shè)置回調(diào)對(duì)象
        enhancer.setCallback(callback);
        // 創(chuàng)建代理對(duì)象
        ConcreteSubject subject = (ConcreteSubject) enhancer.create();
        subject.doOperation();
    }
}
//回調(diào)對(duì)象 實(shí)現(xiàn) MethodInterceptor
public class CGLibMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) 
        throws Throwable {
        Object ret = null;
        System.out.println("CGLib before ConcreteSubject doOperation...");
        ret = proxy.invokeSuper(obj, args);
        System.out.println("CGLib after ConcreteSubject doOperation...");
        return ret;
    }
}
//回調(diào)對(duì)象 實(shí)現(xiàn) InvocationHandler
public class CGLibInvocationHandler implements InvocationHandler {
    
        private Object realSubject;

        public CGLibInvocationHandler(Object realSubject) {
            super();
            this.realSubject = realSubject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
            System.out.println("CGLib before ConcreteSubject doOperation...");
            Object ret = method.invoke(realSubject, args);
            System.out.println("CGLib after ConcreteSubject doOperation...");
            return ret;
        }
    }
//被代理類
public class ConcreteSubject {
    public void doOperation() {
        System.out.println("ConcreteSubject doOperation...");
    }
}

輸出如下:

CGLib before ConcreteSubject doOperation...
ConcreteSubject doOperation...
CGLib after ConcreteSubject doOperation...

為了進(jìn)一步理解 CGLib 動(dòng)態(tài)代理的生成機(jī)制,我們不妨將生成的動(dòng)態(tài)代理類保存到文件中,可以通過設(shè)置:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E://CGLib//proxy//");

來導(dǎo)出動(dòng)態(tài)代理類,使用Java Decompiler工具查看,結(jié)果如下:

public class ConcreteSubject$$EnhancerByCGLib$$7e8b8caf
    extends ConcreteSubject implements Factory {
    
    private MethodInterceptor CGLib$CALLBACK_0;

    final void CGLib$doOperation$0() {
        super.doOperation();
    }
    
    @Override
    public final void doOperation(){
        MethodInterceptor tmp4_1 = this.CGLib$CALLBACK_0;
        if (tmp4_1 == null){
          tmp4_1;
          CGLib$BIND_CALLBACKS(this);
        }
        if (this.CGLib$CALLBACK_0 != null) {
          return;
        }
        super.doOperation();
    }
    @Override
    public final boolean equals(Object paramObject)...
    @Override
    public final String toString()...
    @Override
    public final int hashCode()...
    @Override
    public final Object clone()...
}

通過反編譯得到代碼可以看出,CGLib 是通過繼承被代理類 ConcreteSubject 實(shí)現(xiàn)動(dòng)態(tài)代理的,這也就要求被代理的類不能是 final 類。此外,與 JDK 動(dòng)態(tài)代理相比,CGLib 不僅重寫了 Object 類的 hashCode、equals、toString方法,還重寫了 clone 方法。

此外,值得注意的是創(chuàng)建回調(diào)對(duì)象時(shí),采用實(shí)現(xiàn) MethodInterceptor 接口與 InvocationHandler 接口這兩種方式除了是否需要額外創(chuàng)建被代理對(duì)象以及方法調(diào)用的差異外,還有一個(gè)小細(xì)節(jié):采用實(shí)現(xiàn) InvocationHandler 接口的方式生成的代理類在調(diào)用的方法內(nèi)部如果還調(diào)用該代理類的其他成員方法時(shí),會(huì)對(duì)被調(diào)用的其他方法進(jìn)行代理,而采用 MethodInterceptor 接口方式不會(huì)。(ps: JDK 動(dòng)態(tài)代理也不會(huì)對(duì)非目標(biāo)方法進(jìn)行代理。)

為被代理類添加兩個(gè)方法:

public class ConcreteSubject {

    public static void fn() {
        System.out.println("fn");
    }

    public void doOperation() {
        System.out.println("ConcreteSubject doOperation...");
        other();// InvocationHandler 同時(shí)會(huì)代理非目標(biāo)方法
        fn();//靜態(tài)方法不代理
    }

    public void other() {
        System.out.println("ConcreteSubject other...");
    }
}

相應(yīng)的輸出如下:

invocationHandler
CGLib before RealSubject doOperation...
ConcreteSubject doOperation...
ConcreteSubject other...
fn
CGLib after RealSubject doOperation...
==================
methodInterceptor
Cglib before ConcreteSubject doOperation...
ConcreteSubject doOperation...
Cglib before ConcreteSubject doOperation...
ConcreteSubject other...
Cglib after ConcreteSubject doOperation...
fn
Cglib after ConcreteSubject doOperation...

CGLib 生成動(dòng)態(tài)代理的兩種方式的區(qū)別總結(jié)如下:

項(xiàng)目 MethodInterceptor InvocationHandler
是否依賴被代理對(duì)象實(shí)例 不依賴 依賴
目標(biāo)方法執(zhí)行方式 method.invokeSuper(proxy, args) method.invoke(realSubject, args);
非目標(biāo)方法是否進(jìn)行代理 不代理 代理

JDK 與 CGLib 動(dòng)態(tài)代理的區(qū)別

結(jié)合 JDK 動(dòng)態(tài)代理的實(shí)現(xiàn),可以得出下列區(qū)別:

項(xiàng)目 JDK CGLib
被代理對(duì)象的要求 必須實(shí)現(xiàn)接口(可為 final 類) 非final類
代理類生成方式 繼承 Proxy,實(shí)現(xiàn)被代理類的所有接口 繼承被代理類,實(shí)現(xiàn) Factory 接口
非目標(biāo)方法是否進(jìn)行代理 不進(jìn)行代理 可通過 InvocationHandler 進(jìn)行代理

至此,動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式及其原理已經(jīng)介紹完畢,在理解了相關(guān)原理后,我們完全可以通過反射及Java動(dòng)態(tài)編譯技術(shù)實(shí)現(xiàn)動(dòng)態(tài)代理。

既然JDK動(dòng)態(tài)代理要求被代理類必須實(shí)現(xiàn)接口,而CGLib要求被代理類不能是final類,那么能不能為沒有實(shí)現(xiàn)接口的final類進(jìn)行動(dòng)態(tài)代理呢?

答案是不能,但是可以通過反射來實(shí)現(xiàn)類似動(dòng)態(tài)代理的功能,只需要將Proxy進(jìn)行改造即可,如:

public class MyProxy {
    protected InvocationHandler invocationHandler;
    public MyProxy(InvocationHandler invocationHandler) {
        super();
        this.invocationHandler = invocationHandler;
    }
    public static Object newProxyInstance(ClassLoader loader, 
                                          Class<?> clz, InvocationHandler h){
        ...
    }
    public Object invoke(String methodName, Object... args){
        Class<?>[] parameterTypes = getParameterTypes(args);
        Method method = this.getClass().getDeclaredMethod(methodName,parameterTypes);
        return method.invoke(this, args);
    }
}

使用時(shí)通過 MyProxy 的 invoke 方法實(shí)現(xiàn)被代理對(duì)象方法的調(diào)用:

MyProxy proxy = (MyProxy) MyProxy.newProxyInstance(
                                        FinalSubject.class.getClassLoader(),
                                        FinalSubject.class,
                                        new FinalInvocationHandler(new FinalSubject()));
proxy.invoke("doOperation");
proxy.invoke("other",args);

完整項(xiàng)目可以參考本人的 github 小項(xiàng)目 DynamicProxy

update: 2019-03-10 11:04:25

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容