一文帶你搞懂Java動態代理

在說動態代理之前,先來簡單看下代理模式。代理是最基本的設計模式之一。它能夠插入一個用來替代“實際”對象的“代理”對象,來提供額外的或不同的操作。這些操作通常涉及與“實際”對象的通信,因此“代理”對象通常充當着中間人的角色。

代理模式

代理對象爲“實際”對象提供一個替身或佔位符以控制對這個“實際”對象的訪問。被代理的對象可以是遠程的對象,創建開銷大的對象或需要安全控制的對象。來看下類圖:

在這裏插入圖片描述
再來看下類圖對應代碼,這是IObject接口,真實對象RealObj和代理對象ObjProxy都實現此接口:

/**
 * 爲實際對象Tested和代理對象TestedProxy提供對外接口
 */
public interface IObject {
    void request();
}

RealObj是實際處理request() 邏輯的對象,但是出於設計的考量,需要對RealObj內部的方法調用進行控制訪問

public class RealObject implements IObject {

    @Override
    public void request() {
        // 模擬一些操作
    }
}

ObjProxy是RealObj的代理類,其同樣實現了IObject接口,所以具有相同的對外方法。客戶端與RealObj的所有交互,都必須通過ObjProxy。

public class ObjProxy implements IObject {
    IObject realT;

    public ObjProxy(IObject t) {
        realT = t;
    }

    @Override
    public void request() {
        if (isAllow())
            realT.request();
    }

    /**
     * 模擬針對請求的校驗判斷
     */
    private boolean isAllow() {
        return true;
    }
}
番外

代理模式和裝飾者模式不管是在類圖,還是在代碼實現上,幾乎是一樣的,但我們爲何還要進行劃分呢?其實學設計模式,不能拘泥於格式,不能死記形式,重要的是要理解模式背後的意圖,意圖只有一個,但實現的形式卻可能多種多樣。這也就是爲何那麼多變體依然屬於xx設計模式的原因。

代理模式的意圖是替代真正的對象以實現訪問控制,而裝飾者模式的意圖是爲對象加入額外的行爲。

動態代理

Java的動態代理可以動態的創建代理並動態的處理所代理方法的調用,在動態代理上所做的所以調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型並確定相應的策略。類圖見下:

在這裏插入圖片描述

還以上面的代碼爲例,這是對外的接口IObject:

public interface IObject {
    void request();
}

這是 InvocationHandler 的實現類,類圖中 Proxy 的方法調用都會被系統傳入此類,即 invoke 方法,而 ObjProxyHandler 又持有着 RealObject 實例,所以 ObjProxyHandler 是“真正”對 RealObject 對象進行訪問控制的代理類。

public class ObjProxyHandler implements InvocationHandler {
    IObject realT;

    public ObjProxyHandler(IObject t) {
        realT = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // request方法時,進行校驗
        if (method.getName().equals("request") && !isAllow())
            return null;
        return method.invoke(realT, args);
    }

    /**
     * 模擬針對請求的校驗判斷
     */
    private boolean isAllow() {
        return false;
    }
}

RealObj是實際處理request() 邏輯的對象。

public class RealObject implements IObject {
    @Override
    public void request() {
        // 模擬一些操作
    }
}

動態代理的使用方法如下:我們通過 Proxy.newProxyInstance 靜態方法來創建代理,其參數如下,一個類加載器、一個代理實現的接口列表、一個 InvocationHandler 的接口實現。

    public void startTest() {
        IObject proxy = (IObject) Proxy.newProxyInstance(
                IObject.class.getClassLoader(),
                new Class[]{IObject.class},
                new ObjProxyHandler(new RealObject()));
        proxy.request(); // ObjProxyHandler的invoke方法會被調用
    }
Proxy源碼

來看下Proxy 源碼,當我們 newProxyInstance(…) 時,首先系統會進行判空處理,之後獲取我們實際的 Proxy 代理類 Class 對象,再通過一個參數的構造方法生成我們的代理對象 p(p : 返回值),這裏能看出來 p 是持有我們的對象 h 的。注意 cons.setAccessible(true) 表示,即使是 cl 是私有構造,也可以獲得對象。源碼見下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
     
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        ...
        final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                cons.setAccessible(true);
                // END Android-removed: Excluded AccessController.doPrivileged call.
            }
            return cons.newInstance(new Object[]{h});
        ...
    }

其中 getProxyClass0(…) 是用來檢查並獲取實際代理對象的。首先會有一個65535的接口限制檢測,隨後從代理緩存proxyClassCache 中獲取代理類,如果給定的接口不存在,則通過 ProxyClassFactory 新建。見下:

    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);
    }

存放代理 Proxy.class 的緩存 proxyClassCache,是一個靜態常量,所以在我們類加載時,其就已經被初始化完畢了。見下:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

Proxy 提供的 getInvocationHandler(Object proxy)方法和 invoke(…) 方法很重要。分別爲獲取當前代理關聯的調用處理器對象 InvocationHandler,並將當前Proxy方法調用 調度給 InvocationHandler。是不是與上面的代理思維很像,至於這兩個方法何時被調用的,推測是寫在了本地方法內,當我們調用proxy.request 方法時(系統創建Proxy時,會自動 implements 用戶傳遞的接口,可以爲多個),系統就會調用Proxy invoke 方法,隨後proxy 將方法調用傳遞給 InvocationHandler。

public static InvocationHandler getInvocationHandler(Object proxy)
        throws IllegalArgumentException
    {
        /*
         * Verify that the object is actually a proxy instance.
         */
        if (!isProxyClass(proxy.getClass())) {
            throw new IllegalArgumentException("not a proxy instance");
        }
        final Proxy p = (Proxy) proxy;
        final InvocationHandler ih = p.h;
   
        return ih;
    }

    // Android-added: Helper method invoke(Proxy, Method, Object[]) for ART native code.
    private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
        InvocationHandler h = proxy.h;
        return h.invoke(proxy, method, args);
    }
ProxyClassFactory

重點是ProxyClassFactory 類,這裏的邏輯不少,所以我將ProxyClassFactory 單獨抽出來了。能看到,首先其會檢測當前interface 是否已被當前類加載器所加載。

                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }

之後會進行判斷是否爲接口。這也是我們說的第二個參數爲何不能傳基類或抽象類的原因。

                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }

之後判斷當前 interface 是否已經存在於緩存cache內了。

                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }

檢測非 public 修飾符的 interface 是否在是同一個包名,如果不是則拋出異常

            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

檢驗通過後,會 getMethods(…) 獲取接口內的全部方法。

隨後會對methords進行一個排序。具體的代碼我就不貼了,排序規則是:如果方法相等(返回值和方法簽名一樣)或同是一個接口內方法,則當前順序不變,如果兩個方法所在的接口存在繼承關係,則父類在前,子類在後。

之後 validateReturnTypes(…) 判斷 methords 是否存在方法簽名相同並且返回值類型也相同的methord,如果有則拋出異常。

之後通過 deduplicateAndGetExceptions(…) 方法,將 methords 方法內的相同方法的父類方法剔除掉,並將 methord 保存在數組中。

轉成一維數組和二維數組,Method[] methodsArray,Class< ? >[][] exceptionsArray,隨後給當前代理類命名:包名 + “$Proxy” + num

最後調用系統提供的 native 方法 generateProxy(…) 。這是真正的代理類創建方法。感興趣的可以查看下java_lang_reflect_Proxy.cc源碼
class_linker.cc源碼

                List<Method> methods = getMethods(interfaces);
                Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
                validateReturnTypes(methods);
                List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

                Method[] methodsArray = methods.toArray(new Method[methods.size()]);
                Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;

                return generateProxy(proxyName, interfaces, loader, methodsArray,
                                     exceptionsArray);

參考
1、Head First 設計模式:中國電力出版社

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章