動態代理類(翻譯)

原文: Dyanmic Proxy Classes

介紹

一個動態代理類是實現了多個接口存在于運行時的類,這樣,一個動態代理類所實現的接口的方法的調用會被編譯并且通過一個統一的接口分配給另一個對象。因此,一個動態代理類可以在不預先生成代理類的情況下創建一個類型安全又實現了多個接口的代理對象,就像使用compile-time工具。動態代理類對象的方法調用被分配給InvocationHandler的對象的一個單獨的方法,然后通過java.lang.reflect.Method的對象根據參數確定方法編譯和執行。

應用或者是類庫需要類型安全的反射傳遞、對象調用來實現接口API時,使用動態代理會很有幫助。例如,一個應用可以利用動態代理創建一個實現了多個任意事件監聽接口的對象,而這些接口都是擴展了java.util.EventListener接口,這樣這個對象就可以用一種統一的風格來處理不同類型的事件,比如把所有這種事件記錄到日志文件中。

動態代理類API

動態代理類(以下用代理類表示)是實現了多個接口并在運行時創建的類。

代理接口指被代理類實現的接口。

代理對象指代理類的實例對象。

創建代理類

代理類,代理對象用java.lang.reflect.Proxy的靜態方法創建。

Proxy.getProxyClass方法用代理類的類加載器和一個接口數組返回一個java.lang.Class對象。代理類將根據指定的類加載器定義并且實現所有指定的接口。如果類加載器已經存在了定義過的實現了相同排列接口的代理類,則返回該代理類。其它情況下,實現了這些接口的代理類會動態的生成并在指定的加載器中定義。

傳遞給Proxy.getProxyClass的參數有如下一些限制:

  • 接口數組中的所有Class對象必須表示的是接口,不是類或原始類型。
  • 接口數組中不能有兩個元素指向相同的Class對象。
  • 所有的接口類型對于指定的類加載器必須是可視的,換句話說,對于類加載器cl和每一個接口i,Class.forName(i.getName(),false,cl)==i必須為true。
  • 所有非共有的接口必須在相同的包下,否則代理類實現所有的接口則不可能,不管定義的是哪個包。
  • 對任何指定的接口的成員方法集擁有相同的特征:
    • 如果有一個方法返回的類型是原始類型或void,則所有的方法必須指定相同的類型。
    • 另外,任何一個方法的返回類型必須是可以指定給其它方法的返回類型。
  • 生成的代理類必須不超過虛擬機對類的任何限制。比如,虛擬機可以限制一個類的實現接口數為65535,則接口的數組大小必須不超過65535。

違法上面的任何一項限制,Proxy.getProxyClass將拋出IllegalArgumentException。如果接口數組參數或者其中的元素有null,則會拋出NullPointerException

需要注意指定的代理接口的順序是有意義的: 兩個用相同的接口組合但順序不同的代理類的請求會生成兩個不同的代理類。代理類因為代理接口的順序不同而有區別是為了提供確定的方法調用編碼,防止兩個或更多的接口共用同一個有相同名字和參數標識的方法。詳細的描述可以參閱多個代理接口的重復方法。

所以通過Proxy.getProxyClass傳遞相同的類加載器和接口并不誰生成一個新的代理類,動態代理類的實現應該存在一個代理類的緩存中,關鍵字就是與其一致的類加載器和接口序列。代理類的實現除了應該注意類加載器、接口外,還要注意這種方式的的代理類在占用時同時會阻止類的加載器及其含有的所有類被垃圾回收。

代理類的性質

一個代理類含有如下的性質:

  • 代理類是public,final且非abstract.
  • 代理類的絕對名字沒有詳細指定,但為代理類的名字保留以字符串"$Proxy"開頭。
  • 代理類繼承了java.lang.reflect.Proxy。
  • 代理類在創建的過程中完整有序實現了指定的接口。
  • 如果一個代理類實現了一個non-public的接口,則代理類會生成在該接口所在的包下。其它情況,代理類的包未被指定。注意在運行時閉包行為不會阻止一個代理類在特定包下的成功定義,已經在相同的有特定簽名的包下和相同的類加載器中定義的類也不會。
  • 因為代理類在創建時實現了所有指定的接口,所以代理類的Class對象可以調用getInterfaces得到一個包含相同序列的接口數組(按代理類創建時指定的序列),調用getMethods得到包含所有實現接口的所有方法對象的數組,調用getMethod得到想要的代理接口中的方法對象。
  • Proxy.isProxyClass執行時當傳遞的代理類是通過Proxy.getProxyClass返回的一個類或通過Proxy.newProxyInstance返回的指定代理類的對象則為true,其它情況為false。這個方法的可靠性對制定安全決策是重要的,所以它的實現不僅僅可以測試一個繼承java.lang.reflect.Proxy的類的問題。
  • 代理類的java.security.ProtectionDomain和被引導類載入器裝在的系統類的保護域相同,比如java.lang.Object,因為代理類的代碼是被信任的系統代碼生成的。這個保護域會典型的被授予java.security.AllPermission。

創建代理對象

每個代理類有一個public,參數為一個實現了InvocationHandler接口的對象的構造方法。

每個代理對象含有一個關聯的通過構造方法傳進來的InvocationHandler對象。代理對象的創建除了用反射的API進入public構造方法實現外,還可以調用Proxy.newProxyInstance方法來實現,這個方法綜合了Proxy.getProxyClass并調用了其含有InvocationHandler參數的構造方法。Proxy.newProxyInstance如果拋出IllegalArgumentException,原因和Proxy.getProxyClass的相同。

代理對象的性質

代理對象有如下的性質:

  • 創建一個代理對象proxy,它的代理類實現的一個接口Foo,proxy instanceof Foo會返回true; (Foo) proxy類型轉換會成功(不會拋出ClassCastException)。

  • 靜態方法Proxy.getInvocationHandler會返回代理對象在創建時關聯的InvocationHandler對象。如果傳給Proxy.getInvocationHandler的參數不是一個代理對象,則會拋出IllegalArgumentException。

  • 代理對象調用的接口方法會被編譯并分配給InvocationHandler對象的invoke方法,詳細描述:

    代理對象自身會作為invoke方法的第一個參數傳值,類型是Object。

    invoke方法的第二個參數傳的是與代理對象調用的接口方法一致的java.lang.reflect.Method對象。聲明的方法類是接口中已經定義過的產生的對象,方法類也可以是代理類通過繼承父代理接口里的。

    invoke的第三個參數是傳入的代理對象的方法調用的對象數組。原始類型應該用包裝類的Class對象,例如java.lang.Integerjava.lang.Booleaninvoke方法的實現可以靈活的修改數組的內容。

    invoke方法的返回代理對象調用的方法的返回值。如果接口中方法聲明的返回類型是原始類型,則invoke返回的值必須是初始類型的包裝類的對象;其它情況,則必須是已經聲明過的返回類型。如果ivoke返回的null且接口方法返回的類型是原始類型,則代理對象調用該方法會拋出NullPointerException。如果invoke方法返回的類型超過聲明的類型,則代理對象會拋出ClassCastException

    如果invoke方法拋出一個異常,則代理對象調用對應的方法時也會拋出異常。拋出的異常類型必須是接口方法已經聲明的或是非檢查異常類型java.lang.RuntimeException或java.lang.Error。如果invoke方法拋出的異常是一個檢查異常并且沒有在接口方法聲明,則代理對象調用方法時會拋出UndeclaredThrowableException。UndeclaredThrowableException會被invoke方法拋出的異常重構。

  • 代理對象中調用hashCode,equals或者是toString等在java.lang.Object中定義的方法會被編碼并分配給InvocationHandlerinvoke方法,和上面描述的接口方法的調用被編碼和分配的方式一樣。聲明的方法類對象傳給invoke時是java.lang.Object。代理對象從java.lang.Object繼承的其它放不會被代理類重寫,所以調用這些方法就像java.lang.Object的對象調用。

多個代理接口的重復方法

當一個代理類實現的兩個或更多的接口中包含相同名字和參數標簽的方法時,代理類的實現的接口的順序就特別的重要。當重復的方法被代理對象調用時,方法類對象傳遞給InvocationHandler不一定是代理對象調用的接口的含有相同標簽的方法。這個限制的存在是因為代理類的生成的相同的方法不能決定那個方法會被調用。因此,當代理對象調用重復的方法時,代理中包含該方法的最靠前的接口(不管是直接定義的還是從父接口中繼承的)的方法對象會被傳遞給InvocationHandler對象的invoke方法,忽略已經傳進來的方法調用的參數類型。

如果代理對象包含一個方法與hashCode, equals或者toStringjava.lang.Object中的方法擁有相同的名字和參數標志,那代理對象調用該方法時,傳遞給InvocationHandler對象的invoke方法是聲明該方法的類中的java.lang.Object方法。換句話說,java.lang.Objectpublic,non-final方法邏輯上高于傳給InvocationHandler的代理接口的方法。

注意當一個重復的方法傳遞給InvocationHandler時,invoke方法可能只會拋出可以調用的代理接口的方法拋出的異常。如果invoke方法拋出一個沒有在可以調用的代理的接口的方法中指定的檢查異常,則代理對象調用時會拋出UndeclaredThrowableException。這個限制表明通過getExceptionTypes返回的所有的傳遞給invoke方法的方法對象的異常并不都會被成功的拋出。

序列化

由于java.lang.reflect.Proxy實現了java.io.Serializable接口,所以代理對象是可以序列化的。然而,如果代理對象包含的InvocationHandler沒有實現java.io.Serializable,則代理對象被寫入java.io.ObjectOutputStream是會拋出java.io.NotSerializableException。對于代理類,實現java.io.Externalizable與實現java.io.Serializable的效果相同:Externalizable接口的方法writeExternalreadExternal永遠不會再代理對象的序列化過程中被調用。就像所有的Class對象,代理類的Class對象也是可以序列化的。

一個代理類沒有序列化的變量和一個serialVersionUID。即,代理類的Class對象傳遞給java.io.objectStreamClass的靜態方法lookup,返回的ObjectStreamClass對象有下面的性質:

  • 調用它的getSerialVersionUID將方法返回OL。
  • 調用它的getFields方法會返回一個長度為0的數組。
  • 調用它的getField``方法參數為任意的String將返回null

支持對象序列化的流協議支持一個名字為TC_PROXYCLASSDESC的類型代碼,它是流格式語法的一個終止符號。java.io.ObjectStreamConstants接口中定義了它的類型和值:

`final static byte TC_PROXYCLASSDESC = (byte)0x7D;`

語法還包括連個規則,第一個是替代擴展原始newClassDesc規則:

newClassDesc:

TC_PROXYCLASSDESC newHandle proxyClassDescInfo

proxyClassDescInfo:

(int)<count> proxyInterfaceName[count] classAnnotation superClassDesc

proxyInterfaceName: (utf)

ObjectOutputStream序列化一個類,可以用Proxy.isProxyClass判斷Class對象是否是代理類。如果是代理類,則用TC_PROXYCLASSDESC類型碼代替TC_CLASSDESC。proxyClassDescInfo的擴展中,proxyInterfaceName的隊列是代理類實現的所有接口的名字,順序是Class對象調用getInterfaces返回的順序。classAnnotationsuperClassDesc執行classDescInfo規則時意義相同。對于代理類,superClassDesc是父類的描述符。java.lang.reflect.Proxy,包括序列化的代理類和它演變的代理對象。

對于不是代理類的類,ObjectOutputStream調用它的protectedannotateClass方法允許它的子類為一個特定類的流寫入自定義數據。對于代理類,則會用java.io.ObjectOutputStream下面的方法代替annotateClass:

protected void annotateProxyClass(Class cl) throws IOException;

實現ObjectOutputStream的默認annotateProxyClass方法不做任何操作。

ObjectInputStream遇到類型符TC_PROXYCLASSDESC,表示反序列化流中的代理類。對于代理類,java.io.ObjectInputStream根據類的描述符區分,并用下面的方法替代resolveClass來解析Class對象:

protected Class resolveProxyClass(String[] interfaces)
    throws IOException, ClassNotFoundException;

要反序列化的代理類實現的接口名字的數組作為參數傳遞給resolveProxyClass方法。

ObjectInputStream的實現的默認resolveProxyClass方法返回值是調用Proxy.getProxyClass``的Class對象的interfaces參數的接口名字列表。每個用到的Class對象的接口名字i調用下面方法得到:

Class.forName(i, false, loader)

loader是執行棧中第一個非空的類加載器,如果執行棧中沒有非空的類加載器則傳null。這是resolveClass方法默認的選擇類加載器的行為。和傳遞的Proxy.getProxyClass的類加載器的值相同。如果Proxy.getProxyClass拋出IllegalArgumentException,則resolveClass會拋出一個包含IllegalArgumentExceptionClassNotFoundException異常。因為代理類沒有自己的序列化屬性,代理對象的流的classdata[]包含了完整的對象數據,包含它的父類java.lang.reflect.Proxy。Proxy有一個序列化的屬性,h,包含了代理對象的InvocationHandler。

示例

這是一個在一個實現了任意接口的對象的方法的執行前后打印一條信息的簡單例子:

public interface Foo {
Object bar(Object obj) throws BazException;
}

public class FooImpl implements Foo {
Object bar(Object obj) throws BazException {
    // ...
    }
}

public class DebugProxy implements java.lang.reflect.InvocationHandler {

    private Object obj;

    public static Object newInstance(Object obj) {
        return java.lang.reflect.Proxy.newProxyInstance(
            obj.getClass().getClassLoader(),
            obj.getClass().getInterfaces(),
            new DebugProxy(obj));
    }

    private DebugProxy(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args)
        throws Throwable
    {
        Object result;
        try {
            System.out.println("before method " + m.getName());
            result = m.invoke(obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw new RuntimeException("unexpected invocation exception: " +
                                   e.getMessage());
        } finally {
            System.out.println("after method " + m.getName());
        }
        return result;
    }
}

構造一個實現了接口FooDebugProxy,并調用它的方法

Foo foo = (Foo) DebugProxy.newInstance(new FooImpl());
foo.bar(null);

這是一個實用的InvocationHandler的例子,提供了繼承于java.lang.Object的默認的代理方法行為,實現了特定代理的委托的方法的調用以區別與那些依靠接口調用方法的對象:

import java.lang.reflect.*;

public class Delegator implements InvocationHandler {

// preloaded Method objects for the methods in java.lang.Object
private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
static {
    try {
        hashCodeMethod = Object.class.getMethod("hashCode", null);
        equalsMethod =
            Object.class.getMethod("equals", new Class[] { Object.class });
        toStringMethod = Object.class.getMethod("toString", null);
    } catch (NoSuchMethodException e) {
        throw new NoSuchMethodError(e.getMessage());
    }
}

private Class[] interfaces;
private Object[] delegates;

public Delegator(Class[] interfaces, Object[] delegates) {
    this.interfaces = (Class[]) interfaces.clone();
    this.delegates = (Object[]) delegates.clone();
}

public Object invoke(Object proxy, Method m, Object[] args)
    throws Throwable
{
    Class declaringClass = m.getDeclaringClass();

    if (declaringClass == Object.class) {
        if (m.equals(hashCodeMethod)) {
            return proxyHashCode(proxy);
        } else if (m.equals(equalsMethod)) {
            return proxyEquals(proxy, args[0]);
        } else if (m.equals(toStringMethod)) {
            return proxyToString(proxy);
        } else {
            throw new InternalError(
                "unexpected Object method dispatched: " + m);
        }
    } else {
        for (int i = 0; i < interfaces.length; i++) {
            if (declaringClass.isAssignableFrom(interfaces[i])) {
                try {
                    return m.invoke(delegates[i], args);
                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                }
            }
        }

        return invokeNotDelegated(proxy, m, args);
    }
}

protected Object invokeNotDelegated(Object proxy, Method m,
                                    Object[] args)
    throws Throwable
{
    throw new InternalError("unexpected method dispatched: " + m);
}

protected Integer proxyHashCode(Object proxy) {
    return new Integer(System.identityHashCode(proxy));
}

protected Boolean proxyEquals(Object proxy, Object other) {
    return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
}

protected String proxyToString(Object proxy) {
    return proxy.getClass().getName() + '@' +
        Integer.toHexString(proxy.hashCode());
}
}

Delegator的父類可以重寫invokeNotDelegated實現調用的代理方法的行為而不用直接委托給其它對象。也可以重寫proxyHashCode,proxyEqualsproxyToString,重寫代理類從java.lang.Object繼承的方法的默認行為。

構造一個實現了Foo接口的Delegator

Class[] proxyInterfaces = new Class[] { Foo.class };
Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
    proxyInterfaces,
    new Delegator(proxyInterfaces, new Object[] { new FooImpl() }));

注意實現的Delegator類更多是一個說明并不完善;例如,為hashCode,equalstoString替換緩存和比較的Method對象,它只是能根據它們的名字匹配,因為java.lang.Object中沒用重載任何這些方法。

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

推薦閱讀更多精彩內容