詳解Java動態代理機制

?????之前介紹的反射和注解都是Java中的動態特性,還有即將介紹的動態代理也是Java中的一個動態特性。這些動態特性使得我們的程序很靈活。動態代理是面向AOP編程的基礎。通過動態代理,我們可以在運行時動態創建一個類,實現某些接口中的方法,目前為止該特性已被廣泛應用于各種框架和類庫中,例如:Spring,Hibernate,MyBatis等。理解動態代理是理解框架底層的基礎。
?????主要內容如下:

  • 理解代理是何意
  • Java SDK實現動態代理
  • 第三方庫cglib實現動態代理

一、代理的概念
?????單從字面上理解,代理就是指原對象的委托人,它不是原對象但是卻有原對象的權限。Java中的代理意思類似,就是指通過代理來操作原對象的方法和屬性,而原對象不直接出現。這樣做有幾點好處:

  • 節省創建原對象的高開銷,創建一個代理并不會立馬創建一個實際原對象,而是保存一個原對象的地址,按需加載
  • 執行權限檢查,保護原對象
這里寫圖片描述

實際上代理堵在了原對象的前面,在代理的內部往往還是調用了原對象的方法,只是它還做了其他的一些操作。下面看第一種實現動態代理的方式。

二、Java SDK實現動態代理
?????實現動態代理主要有如下幾個步驟:

  • 實現 InvocationHandler接口,完成自定義調用處理器
  • 通過Proxy的getProxyClass方法獲取對應的代理類
  • 利用反射技術獲取該代理類的constructor構造器
  • 利用constructor構造代理實例對象

在一步步解析源碼之前,我們先通過一個完整的實例了解下,整個程序的一步步邏輯走向。

//定義了一個調用處理器
public class MyInvotion implements InvocationHandler {

    private Object realObj;

    public MyInvotion(Object obj){
        this.realObj =  obj;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //通過代理執行原對象的方法
        return method.invoke(realObj,args);
    }
}
//定義一個接口
public interface MyInterface {
    
    public void sayHello();
}

//該接口的一個實現類,該類就是我們的原對象
public class ClassA implements MyInterface {

    public void sayHello(){
        System.out.println("hello walker");
    }
}
//main 函數
public static void main(String[] args){
        ClassA a = new ClassA();
        MyInvotion myInvotion = new MyInvotion(a);
        Class myProxy = Proxy.getProxyClass(ClassA.class.getClassLoader(), new Class[]{MyInterface.class});
        Constructor constructor =myProxy.getConstructor(new Class[]{InvocationHandler.class});
        MyInterface m = (MyInterface)constructor.newInstance(myInvotion);
        m.sayHello();
    }
輸出結果:hello walker

簡單說下整體的運行過程,首先我們創建ClassA 實例并將它傳入自定義的調用處理器MyInvotion,在MyInvotion中用realObj接受該參數代表原對象。接著調用Proxy的getProxyClass方法,將ClassA 的類加載器和ClassA 的實現的接口集合傳入,該方法內部會實現所有接口返回該類的代理類,然后我們利用反射獲取代理類的構造器并創建實例。

以上便是整個程序運行的大致流程,接下來我們從源代碼的角度看看具體是如何實現的。首先我們看InvocationHandler接口,這是我們的調用處理器,在代理類中訪問的所有的方法都會被轉發到這執行,具體的等我們看了代理類源碼及理解了。該接口中唯一的方法是:

public Object invoke(Object proxy, Method method, Object[] args)
  • 參數Proxy表示動態生成的代理類的對象,基本沒啥用
  • 參數method表示當前正在被調用的方法
  • 數組args指定了該方法的參數集合

我們上例中對該接口的實現情況,定義了一個realObj用于保存原對象的引用。重寫的invoke方法中調用了原對象realObj的method方法,具體誰來調用該方法以及傳入的參數是什么,在看完代理類源碼即可知曉。

接下來我們看看最核心的內容,如何動態創建代理類。這是getProxyClass方法的源碼:

    public static Class<?> getProxyClass(ClassLoader loader,
                                         Class<?>... interfaces)
        throws IllegalArgumentException
    {
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        return getProxyClass0(loader, intfs);
    }

首先獲取了該類實現的所有的接口的集合,然后判斷創建該代理是否具有安全性問題,檢查接口類對象是否對類裝載器可見等。然后調用另外一個getProxyClass0方法,我們跟進去:

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

判斷如果該類的接口超過65535(想必還沒有那么牛的類),拋出異常。在我們的Proxy類中有個屬性proxyClassCache,這是一個WeakCache類型的靜態變量。它指示了我們的類加載器和代理類之間的映射。所以proxyClassCache的get方法用于根據類加載器來獲取Proxy類,如果已經存在則直接從cache中返回,如果沒有則創建一個映射并更新cache表。具體創建一個Proxy類并存入cache表中的代碼限于能力,未能參透。

至此我們就獲取到了該ClassA類對應的代理類型,接著我們通過該類的getConstructor方法獲取該代理類的構造器,并傳入InvocationHandler.class作為參數,至于為何要傳入該類型作為參數,等會看代理類源碼變一目了然了。

最后newInstance創建該代理類的實例,實現對ClassA對象的代理。

可能看完上述的介紹,你還會有點暈。下面我們通過查看動態生成的代理類的源碼來加深理解。上述getProxyClass方法會動態創建一個代理類并返回他的Class類型,這個代理類一般被命名為$ProxyN,這個N是遞增的用于標記不同的代理類。我們可以利用反編譯工具反編譯該class:

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

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

    public final boolean equals(Object paramObject) {
        return ((Boolean) this.h.invoke(this, m1, 
                new Object[] { paramObject })).booleanValue();
    }

    public final void sayHello() {
        this.h.invoke(this, m3, null);
    }

    public final String toString() {
        return (String) this.h.invoke(this, m2, null);
    }

    public final int hashCode() {
        return ((Integer) this.h.invoke(this, m0, null)).intValue();
    }

    static {
        m1 = Class.forName("java.lang.Object").getMethod("equals",
                new Class[] { Class.forName("java.lang.Object") });
        m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
                .getMethod("sayHello",new Class[0]);
        m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    }
}

這就是上述的ClassA的動態代理類,我們看到該類的構造方法傳入參數InvocationHandler類型,并調用了父類Proxy的構造方法保存了這個InvocationHandler實例,這也解釋了我們為什么在獲取構造器的時候需要指定參數類型為InvocationHandler,就是因為動態代理類只有一個構造器并且參數類型為InvocationHandler。

接著我們看其中的方法,貌似只有一個sayHello是我們知道的,別的方法哪來的?我們說過在動態創建代理類的時候,會實現原對象的所有接口。所以sayHello方法是實現的MyInterface。而其余的四個方法是代理類由于比較常用,被默認添加到其中。而這些方法的內部都是調用的this.h.invoke這個方法,this.h就是保存在父類Proxy中的InvocationHandler實例(我們用構造器向其中保存的),調用了這個類的invoke方法,在我們自定義的InvocationHandler實例中重寫了invoke方法,我們寫的比較簡單,直接執行傳入的method。

也就是我們調用代理類的任何一個方法都會轉發到該InvocationHandler實例中的involve中,因為該實例中保存有我們的原對象,所以我們可以選擇直接調取原對象中的方法作為回調。

以上便是有關Java SDK中動態代理的相關內容,稍微總結下,首先我們通過實現InvocationHandler自定義一個調用處理類,該類中會保存我們的原對象,并提供一個invoke方法供代理類使用。然后我們通過getProxyClass方法動態創建代理類,最后用反射獲取代理類的實例對象。

需要注意的是:以上我們使用的四步創建代理實例時最根本的,其實Proxy中提供一個方法可以封裝2到4步的操作。上述代碼也可以這么寫:

ClassA a = new ClassA();
MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
aProxy.sayHello();

我們打開該方法的內部源碼,其實走的還是我們上述的過程,它就是做了封裝。

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

    Objects.requireNonNull(h);
        
        //獲取所有接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        
        //創建動態代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
    .............
    ..............
}

二、第三方庫cglib實現動態代理
?????使用動態代理,我們編寫通用的代碼邏輯,即僅實現一個InvocationHandler實例完成對多個類型的代理。但是我們從動態生成的代理類的源碼可以看到,所有的代理類都繼承自Proxy這個類,這就導致我們這種方式不能代理類,只能代理接口。因為java中是單繼承的。也就是說,給我們一個類型,我們只能動態實現該類所有的接口類型,但是該類繼承的別的類我們在代理類中是不能使用的,因為它沒有被代理類繼承。下面看個例子:

public class ClassB {
    public void welcome(){
        System.out.println("welcom walker");
    }
}
public interface MyInterface {

    public void sayHello();
}

//需要被代理的原類型,繼承了ClassB和接口MyInterface 
public class ClassA extends ClassB implements MyInterface {

    public void sayHello(){
        System.out.println("hello walker");
    }
}
//InvocationHandler 實例
public class MyInvotion implements InvocationHandler {

private Object realObj;

public MyInvotion(Object obj){
     this.realObj =  obj;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        return method.invoke(realObj,args);
    }
}

我們反編譯該代理類,和上述的源碼是一樣的,此處不再重復貼出。我們能從中看出來的是,我們的代理只會實現原類型中所有的接口,至于原類型所繼承的類,在生成Proxy代理類的時候會丟棄,因為所有的代理類必須繼承Proxy類,這就導致原類型的父類中的方法 在代理類中丟失。這是該種方式的一大弊端。下面我們看看另一種方式實現動態代理,該種方式完美解決了這種不足。

限于篇幅,我們下篇介紹cglib實現動態代理機制的內容,本篇暫時結束,總結的不好,望海涵。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容