JDK動態(tài)代理

一、基本概念

1.什么是代理产弹?

在闡述JDK動態(tài)代理之前,我們很有必要先來弄明白代理的概念弯囊。代理這個詞本身并不是計算機(jī)專用術(shù)語痰哨,它是生活中一個常用的概念。這里引用維基百科上的一句話對代理進(jìn)行定義:
A proxy is an agent or substitute authorized to act for another person or a document which authorizes the agent so to act.
意思是說:代理指的是一個代理人(或替代品)匾嘱,它被授權(quán)代表另外一個人(或文檔)斤斧。
從這個簡明扼要的定義中,可以看出代理的一些特性:1.代理存在的意義就是代表另一個事物霎烙。2.代理至少需要完成(或?qū)崿F(xiàn))它所代表的事物的功能撬讽。

人與人之間的代理

2.什么是JAVA靜態(tài)代理蕊连?

JAVA靜態(tài)代理是指由程序員創(chuàng)建或工具生成的代理類,這個類在編譯期就已經(jīng)是確定了的游昼,存在的甘苍。
典型的靜態(tài)代理模式一般包含三類角色:
1.抽象角色:它的作用是定義一組行為規(guī)范。抽象角色一般呈現(xiàn)為接口(或抽象類)烘豌,這些接口(或抽象類)中定義的方法就是待實現(xiàn)的载庭。
2.真實角色:實現(xiàn)了抽象角色所定義的行為。真實角色就是個普通的類廊佩,它需要實現(xiàn)抽象角色定義的那些接口囚聚。
3.代理角色:代表真實角色的角色。根據(jù)上面代理的定義标锄,我們可以知道代理角色需要至少完成(或?qū)崿F(xiàn))真實角色的功能顽铸。為了完成這一使命,那么代理角色也需要實現(xiàn)抽象角色所定義的行為(即代理類需要實現(xiàn)抽象角色所定義的接口)料皇,并且在實現(xiàn)接口方法的時候需要調(diào)用真實角色的相應(yīng)方法谓松。

靜態(tài)代理

上圖使用UML類圖解釋了靜態(tài)代理的數(shù)據(jù)模型。
1.接口IFunc代表了抽象角色瓶蝴,定義了一個行為毒返,即方法doSomething()。
2.類RealFunc代表了真實角色舷手,它實現(xiàn)了IFunc接口中定義的方法doSomething()拧簸。
3.類ProxyFunc代表了代理角色,它實現(xiàn)了IFunc接口中定義的方法doSomething()男窟。它的實現(xiàn)方式是依賴RealFunc類的盆赤,通過持有RealFunc類對象的引用realObj,在ProxyFunc.doSomething()方法中調(diào)用了realObj.doSomething()歉眷。當(dāng)然牺六,代理類也可以做一些其他的事情,如圖中的doOtherthing()汗捡。

通過上面的介紹淑际,可以看出靜態(tài)代理存在以下問題:
1.代理類依賴于真實類,因為代理類最根本的業(yè)務(wù)功能是需要通過調(diào)用真實類來實現(xiàn)的扇住。那么如果事先不知道真實類春缕,該如何使用代理模式呢?
2.一個真實類必須對應(yīng)一個代理類艘蹋,即當(dāng)有多個真實類RealA锄贼、RealB、RealC...的時候女阀,就需要多個代理類ProxyA宅荤、ProxyB屑迂、ProxyC...。這樣的話如果大量使用靜態(tài)代理冯键,容易導(dǎo)致類的急劇膨脹惹盼。該如何解決?

要想解決上述問題琼了,就需要使用下面講解的JAVA動態(tài)代理逻锐。

3.什么是JAVA動態(tài)代理?

JAVA動態(tài)代理與靜態(tài)代理相對雕薪,靜態(tài)代理是在編譯期就已經(jīng)確定代理類和真實類的關(guān)系昧诱,并且生成代理類的。而動態(tài)代理是在運行期利用JVM的反射機(jī)制生成代理類,這里是直接生成類的字節(jié)碼所袁,然后通過類加載器載入JAVA虛擬機(jī)執(zhí)行≌档担現(xiàn)在主流的JAVA動態(tài)代理技術(shù)的實現(xiàn)有兩種:一種是JDK自帶的,就是我們所說的JDK動態(tài)代理燥爷,另一種是開源社區(qū)的一個開源項目CGLIB蜈亩。本文主要對JDK動態(tài)代理做討論。

4.什么是JDK動態(tài)代理前翎?

JDK動態(tài)代理的實現(xiàn)是在運行時稚配,根據(jù)一組接口定義,使用Proxy港华、InvocationHandler等工具類去生成一個代理類和代理類實例道川。

JDK動態(tài)代理的類關(guān)系模型和靜態(tài)代理看起來差不多。也是需要一個或一組接口來定義行為規(guī)范立宜。需要一個代理類來實現(xiàn)接口冒萄。區(qū)別是沒有真實類,因為動態(tài)代理就是要解決在不知道真實類的情況下依然能夠使用代理模式的問題橙数。

JDK動態(tài)代理

圖中高亮顯示的$Proxy0即為JDK動態(tài)代理技術(shù)生成的代理類尊流,類名的生成規(guī)則是前綴"$Proxy"加上一個序列數(shù)。這個類繼承Proxy灯帮,實現(xiàn)一系列的接口Intf1,Intf2...IntfN崖技。

既然要實現(xiàn)接口,那么就要實現(xiàn)接口的各個方法钟哥,即圖中的doSomething1(),doSomething2()...doSomethingN()迎献。我們上面介紹靜態(tài)代理的時候,知道靜態(tài)代理類本質(zhì)上是調(diào)用真實類去實現(xiàn)接口定義的方法的瞪醋。但是JDK動態(tài)代理中是沒有真實類這樣的概念的。那么JDK動態(tài)代理類是如何實現(xiàn)這些接口方法的具體邏輯呢装诡?答案就在圖中的InvocationHandler上银受。$Proxy0對外只提供一個構(gòu)造函數(shù)践盼,這個構(gòu)造函數(shù)接受一個InvocationHandler實例h,這個構(gòu)造函數(shù)的邏輯非常簡單宾巍,就是調(diào)用父類的構(gòu)造函數(shù)咕幻,將參數(shù)h賦值給對象字段h。最終就是把所有的方法實現(xiàn)都分派到InvocationHandler實例h的invoke方法上顶霞。所以JDK動態(tài)代理的接口方法實現(xiàn)邏輯是完全由InvocationHandler實例的invoke方法決定的肄程。

二、樣例分析

了解了JDK動態(tài)代理的概念后选浑,現(xiàn)在我們動手寫個JDK動態(tài)代理的代碼樣例蓝厌。直觀的認(rèn)識下JDK動態(tài)代理技術(shù)為我們的做了什么。上面說到了古徒,JDK動態(tài)代理主要依靠Proxy和InvocationHandler這兩個類來生成動態(tài)代理類和類的實例拓提。這兩個類都在jdk的反射包java.lang.reflect下面。Proxy是個工具類隧膘,有了它就可以為接口生成動態(tài)代理類了代态。如果需要進(jìn)一步生成代理類實例,需要注入InvocationHandler實例疹吃。這點我們上面解釋過蹦疑,因為代理類最終邏輯的實現(xiàn)是分派給InvocationHandler實例的invoke方法的。

閑話休絮萨驶,先開始我們的第一步歉摧,定義一個接口。這個接口里面定義一個方法helloWorld()篡撵。

public interface MyIntf {
    void helloWorld();
}

第二步判莉,編寫一個我們自己的調(diào)用處理類,這個類需要實現(xiàn)InvocationHandler接口育谬。

public class MyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method);
        return null;
    }
}

InvocationHandler接口只有一個待實現(xiàn)的invoke方法券盅。這個方法有三個參數(shù),proxy表示動態(tài)代理類實例膛檀,method表示調(diào)用的方法锰镀,args表示調(diào)用方法的參數(shù)。在實際應(yīng)用中咖刃,invoke方法就是我們實現(xiàn)業(yè)務(wù)邏輯的入口泳炉。這里我們的實現(xiàn)邏輯就一行代碼,打印當(dāng)前調(diào)用的方法(在實際應(yīng)用中這么做是沒有意義的嚎杨,不過這里我們只想解釋JDK動態(tài)代理的原理花鹅,所以越簡單越清晰)。

第三步枫浙,直接使用Proxy提供的方法創(chuàng)建一個動態(tài)代理類實例刨肃。并調(diào)用代理類實例的helloWorld方法古拴,檢測運行結(jié)果。

public class ProxyTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        MyIntf proxyObj = (MyIntf)Proxy.newProxyInstance(MyIntf.class.getClassLoader(),new Class[]{MyIntf.class},new MyInvocationHandler());
        proxyObj.helloWorld();
    }
}

第三行代碼是設(shè)置系統(tǒng)屬性真友,把生成的代理類寫入到文件黄痪。這里再強(qiáng)調(diào)一下,JDK動態(tài)代理技術(shù)是在運行時直接生成類的字節(jié)碼盔然,并載入到虛擬機(jī)執(zhí)行的桅打。這里不存在class文件的,所以我們通過設(shè)置系統(tǒng)屬性愈案,把生成的字節(jié)碼保存到文件挺尾,用于后面進(jìn)一步分析。
第四行代碼就是調(diào)用Proxy.newProxyInstance方法創(chuàng)建一個動態(tài)代理類實例刻帚,這個方法需要傳入三個參數(shù)潦嘶,第一個參數(shù)是類加載器,用于加載這個代理類崇众。第二個參數(shù)是Class數(shù)組掂僵,里面存放的是待實現(xiàn)的接口信息。第三個參數(shù)是InvocationHandler實例顷歌。
第五行調(diào)用代理類的helloWorld方法锰蓬,運行結(jié)果:

public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()

分析運行結(jié)果,就可以發(fā)現(xiàn)眯漩,方法的最終調(diào)用是分派到了MyInvocationHandler.invoke方法芹扭,打印出了調(diào)用的方法信息。

到這里赦抖,對于JDK動態(tài)代理的基本使用就算講完了舱卡。我們做的事情很少,只是編寫了接口MyIntf和調(diào)用處理類MyInvocationHandler队萤。其他大部分的工作都是Proxy工具類幫我們完成的轮锥。Proxy幫我們創(chuàng)建了動態(tài)代理類和代理類實例。上面的代碼我們設(shè)置了系統(tǒng)屬性要尔,把生成的字節(jié)碼保存到class文件舍杜。下面我們通過反編譯軟件(如jd-gui),看下Proxy類為我們生成的代理類是什么樣子的赵辕。

package com.sun.proxy;
import com.tuniu.distribute.openapi.common.annotation.MyIntf;
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 MyIntf {
    private static Method m0;
    private static Method m1;
    private static Method m2;
    private static Method m3;

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            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.tuniu.distribute.openapi.common.annotation.MyIntf").getMethod("helloWorld", new Class[0]);
            return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }

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

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

    // 后面省略equals(),hashCode(),toString()三個方法的代碼既绩,因為這三個方法和helloWorld()方法非常相似
}

這里Proxy為我們生成的代理類叫$Proxy0,繼承了Proxy,實現(xiàn)了我們定義的接口MyIntf还惠。每一個JDK動態(tài)代理技術(shù)生成的代理類的名稱都是由$Proxy前綴加上一個序列數(shù)0,1饲握,2...。并且都需要繼承Proxy類。

$Proxy0類中9-26行代碼定義了4個Method字段m0,m1,m2,m3救欧,我們先來看下m3,它描述了我們定義的接口MyIntf中的方法helloWorld歪今。

緊接著下面的32-41行代碼就是對helloWorld方法的實現(xiàn),它的實現(xiàn)非常簡單就一句話this.h.invoke(this, m3, null);這行代碼就是調(diào)用當(dāng)前對象的h實例的invoke方法颜矿,也就是把方法的實現(xiàn)邏輯分派給了h.invoke。這里的h是繼承父類Proxy中的InvocationHandler字段(讀者可以結(jié)合上面的動態(tài)代理類圖模型或者Proxy源碼進(jìn)一步理解)嫉晶。

同時$Proxy0提供了一個構(gòu)造函數(shù)(代碼28-30行)骑疆,調(diào)用父類的構(gòu)造函數(shù)來注入這個InvocationHandler實例。

$Proxy0中的另外3個Method對象m0,m1,m2分別代表了Object類的hashCode(),equals(),toString()方法替废,我們知道java中的所有類都是Object的子類(Object類本身除外)箍铭,這里$Proxy0重寫了Object中的這三個方法。這三個方法的實現(xiàn)和helloWorld方法很類似椎镣,所以筆者這里就把這段代碼省略了诈火,用一行注釋(43行代碼)解釋了下。

行文至此状答,我們已經(jīng)感官的認(rèn)識了運行時生成的代理類結(jié)構(gòu)冷守。揭開了這層面紗,其實JDK動態(tài)代理也沒什么了惊科。簡單的來說就是拍摇,JDK動態(tài)代理技術(shù)可以為一組接口生成代理類,這個代理類也就是一層殼馆截,簡單的實現(xiàn)了接口中定義的方法充活。通過提供一個構(gòu)造函數(shù)傳入InvocationHandler實例,然后將方法的具體實現(xiàn)交給它蜡娶。

三混卵、源碼分析

前兩部分分別從概念和應(yīng)用的角度闡述了JDK動態(tài)代理技術(shù)。最后一部分我們將從Proxy源碼對JDK動態(tài)代理進(jìn)行深入的剖析窖张。Proxy類對外提供了4個靜態(tài)方法幕随,分別為:

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces);
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
public static boolean isProxyClass(Class<?> cl);
public static InvocationHandler getInvocationHandler(Object proxy);

下面我們通過各個方法的源碼依次分析。

1.getProxyClass

getProxyClass方法返回代理類的Class實例荤堪。這個代理類就是類加載器loader定義的合陵、實現(xiàn)了一些列接口interfaces的。如果之前已經(jīng)為這個loader和interfaces創(chuàng)建過代理類澄阳,那么直接返回這個代理類的Class實例拥知。如果沒有,則動態(tài)創(chuàng)建并返回碎赢。

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

getProxyClass方法并沒有JDK動態(tài)代理的核心邏輯:第二行將接口的Class數(shù)組interfaces進(jìn)行克隆低剔。3-6行是類加載器和接口訪問權(quán)限的校驗(這里虛擬機(jī)的安全性相關(guān)邏輯,不是我們JDK代理技術(shù)的關(guān)注點,所以不做過多解釋)襟齿。關(guān)鍵的邏輯就最后一行代碼姻锁,調(diào)用getProxyClass0方法去獲取代理類的Class實例。

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

getProxyClass0也沒有包含JDK動態(tài)代理的核心邏輯:2-4行只是對接口的個數(shù)進(jìn)行了簡單的校驗猜欺,不能超過65535位隶,我們在實際應(yīng)用中一般也不會出現(xiàn)這種情況。最后一行代碼是去緩存對象proxyClassCache中獲取代理類的Class實例开皿。proxyClassCache是Proxy類的靜態(tài)變量

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

proxyClassCache是類java.lang.reflect.WeakCache的實例涧黄,通過類名就可以看出來這個類是用來做緩存的。Proxy調(diào)用WeakCache提供的構(gòu)造函數(shù)赋荆,傳入KeyFactory實例和ProxyClassFactory實例(這兩個實例的用途后面會講到)笋妥。在分析WeakCache的get方法源碼之前,我們先來大概介紹下WeakCache緩存的數(shù)據(jù)結(jié)構(gòu)窄潭。

代理的數(shù)據(jù)結(jié)構(gòu)

WeakCache緩存的數(shù)據(jù)結(jié)構(gòu)是(key,sub-key)->(value)春宣。這個結(jié)構(gòu)和Redis里面的hash結(jié)構(gòu)很類似,根據(jù)一級的鍵(key)嫉你、二級的鍵(sub-key)為索引月帝,去檢索出值(value)。對應(yīng)到WeakCache類代碼里面幽污,就是一個ConcurrentMap實例map嫁赏,這個map的key就是一級鍵,map的value又是個ConcurrentMap實例油挥,這個子map的key是二級鍵潦蝇,子map的value就是緩存的的值。上面圖中的箭頭就表示著對應(yīng)關(guān)系深寥,一目了然攘乒。圖中下半部分是JDK動態(tài)代理緩存的鍵值生成規(guī)則,后面會一一詳解惋鹅。下面我們看下WeakCache的get方法源碼则酝。

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;
    while (true) {
        if (supplier != null) {
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;
            }
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else {
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

我們的調(diào)用語句是proxyClassCache.get(loader, interfaces),也就是說形參key是類加載器loader闰集,parameter是接口的Class數(shù)組interfaces沽讹。

第2行代碼是對形參parameter(interfaces)進(jìn)行非空的校驗,如果為空則拋出空指針異常武鲁。第3行代碼是去除緩存中的陳舊數(shù)據(jù)爽雄,這里不是我們的關(guān)注點,就不詳細(xì)介紹了沐鼠。

第4行是根據(jù)形參key(loader)計算出緩存的一級鍵cacheKey挚瘟,這里我們不去看具體的生成邏輯叹谁,只需要大概知道一級鍵是根據(jù)形參key(loader)算出來的,這里可以用一個數(shù)學(xué)函數(shù)表達(dá)式描述這個關(guān)系:key=f(loader)乘盖。

第5行代碼是根據(jù)一級鍵查出值焰檩,這個值的Map實例valuesMap。由于之前沒有為這個loader和interfaces創(chuàng)建過代理類订框,所以valuesMap為null析苫,6-11行代碼會被執(zhí)行,這幾行代碼就是給valueMap一個初始值穿扳,然后結(jié)合上面算出來的一級鍵cacheKey塞進(jìn)緩存實例map里面藤违。

第12行根據(jù)key(loader)和parameter(interfaces)計算出緩存的二級鍵subKey。這里的subKeyFactory是Proxy調(diào)用WeakCache提供的構(gòu)造函數(shù)時纵揍,傳入的KeyFactory實例(上面提到過)。KeyFactory是Proxy的內(nèi)部類议街,我們簡單看下KeyFactory的apply方法泽谨,看下是怎么生成二級鍵的。

public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]);
        case 2: return new Key2(interfaces[0], interfaces[1]);
        case 0: return key0;
        default: return new KeyX(interfaces);
    }
}

這里筆者不打算展開分析每行代碼特漩,我們通過上面的代碼吧雹,只需要大概知道二級鍵是根據(jù)interfaces計算出來的(classLoader這個參數(shù)根本沒用到)。這里可以用一個數(shù)學(xué)函數(shù)表達(dá)式描述這個關(guān)系:sub-key=g(interfaces)涂身。

我們繼續(xù)上面的WeakCache.get方法分析雄卷,第13行代碼是根據(jù)二級鍵subKey從valuesMap獲取值supplier,這個值supplier也就是我們緩存數(shù)據(jù)的值蛤售。由于valuesMap是新建的丁鹉,所以supplier為null。

15-37行是個循環(huán)悴能,第一次進(jìn)入的時候揣钦,factory和supplier都為null。所以22-30行代碼將被執(zhí)行漠酿。第23行代碼是調(diào)用Factory構(gòu)造函數(shù)創(chuàng)建一個實例factory(Factory是WeakCache的內(nèi)部類)冯凹,這個構(gòu)造函數(shù)就是簡單把傳入的參數(shù)賦值給factory實例的字段。接下來26-29將構(gòu)造的二級鍵subKey和factory塞進(jìn)valuesMap炒嘲,并將factory賦給supplier(Factory類繼承Supplier類)宇姚。到這里緩存數(shù)據(jù)的初始化就算告一段落了,一級鍵是根據(jù)loader計算出來的cacheKey夫凸,二級鍵是根據(jù)interfaces計算出來的subKey浑劳,值是new的一個factory(Supplier實例)。

第一遍的循環(huán)并沒有創(chuàng)建代理類夭拌,只是做了一些初始化的工作呀洲,下面繼續(xù)執(zhí)行這個循環(huán)體(15-37行)。這次supplier不為null了(就是上面的Factory實例factory),所以進(jìn)入16-21行的代碼塊道逗,第17行實質(zhì)就是調(diào)用factory.get方法兵罢。這個方法返回的value也就是動態(tài)代理類的Class實例。緊接著第19行就把這個value返回滓窍。下面來看下Factory的get方法的源碼卖词。

public synchronized V get() {
    Supplier<V> supplier = valuesMap.get(subKey);
    if (supplier != this) {
        return null;
    }
    V value = null;
    try {
        value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    } finally {
        if (value == null) {
            valuesMap.remove(subKey, this);
        }
    }
    assert value != null;
    CacheValue<V> cacheValue = new CacheValue<>(value);
    if (valuesMap.replace(subKey, this, cacheValue)) {
        reverseMap.put(cacheValue, Boolean.TRUE);
    } else {
        throw new AssertionError("Should not reach here");
    }
    return value;
}

第2行代碼是根據(jù)二級鍵subKey得到值supplier,也就是我們在上面的WeakCache的get方法中創(chuàng)建的Factory實例factory吏夯。

接下來的幾行代碼沒什么好講的此蜈,直接看第8行代碼,這行代碼調(diào)用了valueFactory.apply方法創(chuàng)建動態(tài)代理類并將結(jié)果賦值給變量value噪生。

9-14行針對創(chuàng)建代理類失敗的情況下做的處理和判斷邏輯裆赵。如果創(chuàng)建代理類成功,則繼續(xù)執(zhí)行后面的代碼跺嗽。

第15行代碼把生成的代理類的Class實例(即value變量)進(jìn)行緩存战授。緩存的值并不是直接的value,而是由value構(gòu)造的一個CacheValue實例cacheValue桨嫁,由于CacheValue實現(xiàn)了接口Value植兰,而Value接口繼承了Supplier接口,所以cacheValue就是Supplier的實例璃吧。這里我們不需要去深究CacheValue的數(shù)據(jù)結(jié)構(gòu)楣导,只需要知道緩存的值是根據(jù)代理類的Class實例去計算的,這里可以用一個數(shù)學(xué)函數(shù)表達(dá)式描述這個關(guān)系:value=h($ProxyX.class)畜挨。

第16行代碼將二級鍵subKey和cacheValue放入valuesMap(valuesMap的類型是ConcurrentMap<Object, Supplier<V>>)筒繁。第18行是記錄緩存狀態(tài)的。方法的最后將代理類的Class實例value返回巴元。

這個方法的主要邏輯是對緩存的操作膝晾,動態(tài)代理類的創(chuàng)建動作是通過調(diào)用valueFactory.apply得到的。這里的valueFactory是在構(gòu)造WeakCache時傳入的參數(shù)务冕,上面提到的ProxyClassFactory實例血当。由于Factory是WeakCache的內(nèi)部類,所以在Factory的get方法中可以使用這個實例valueFactory禀忆。下面我們就來看下ProxyClassFactory的apply方法臊旭。

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    for (Class<?> intf : interfaces) {
        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");
        }
        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
        }
    }
    String proxyPkg = null;
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    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");
            }
        }
    }
    if (proxyPkg == null) {
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
    }
}

第2-17行代碼,是幾個簡單的校驗工作:1.接口interfaces是否對類加載器loader可見(即接口interfaces是由類加載器loader加載的或者由loader的上一級類加載器加載的箩退,這點需要讀者了解JVM的類加載知識)离熏,2.參數(shù)interfaces是否都是接口,3.參數(shù)interfaces里面有沒有重復(fù)數(shù)據(jù)戴涝。

第18-26行代碼滋戳,是為即將生成的代理類計算出訪問標(biāo)志和包名钻蔑。具體的約束和規(guī)則如下:

1.如果所有的接口的訪問標(biāo)志都是public,那么生成的代理類的訪問標(biāo)志是final public奸鸯,否則是final咪笑。

2.對于訪問標(biāo)志不是public的接口,它們必須要在同一個包下娄涩,否則拋出異常窗怒。

3.如果存在訪問標(biāo)志不是public的接口,那么生成的代理類的包名就是這些接口的包名蓄拣。否則包名是默認(rèn)的ReflectUtil.PROXY_PACKAGE(即com.sun.proxy)扬虚。

第37-38行代碼,是計算代理類的權(quán)限定名球恤。代理類的簡單名稱生成規(guī)則前面介紹過辜昵,是特定前綴"$Proxy"加上一個序列數(shù)(0,1,2,3...)。

第39行調(diào)用ProxyGenerator的靜態(tài)方法generateProxyClass去創(chuàng)建代理類的字節(jié)碼咽斧。這個方法我們就不跟進(jìn)去看了堪置,因為它的邏輯就是根據(jù)一些固化的規(guī)則(比如代理類里面要實現(xiàn)接口的方法,實現(xiàn)Object的equals收厨、hashCode、toString方法优构,提供一個形參為InvocationHandler實例的構(gòu)造函數(shù)等等)诵叁,依據(jù)JAVA虛擬機(jī)規(guī)范中定義的Class類文件結(jié)構(gòu)去生成字節(jié)碼的。我們之前在第二部分“樣例分析”中通過反編譯軟件钦椭,觀察分析過生成的代理類結(jié)構(gòu)拧额。這里讀者可以和前面的內(nèi)容融會貫通一下。

第41行代碼彪腔,調(diào)用Proxy的本地方法defineClass0將生成的代理類字節(jié)碼加載到虛擬機(jī)侥锦,并返回代理類的Class實例。(如果讀者對類加載的只是感興趣的話德挣,可以去深入學(xué)習(xí)下JAVA虛擬機(jī)的類加載機(jī)制)

到目前為止恭垦,JDK動態(tài)代理類的創(chuàng)建流程就全部結(jié)束了,我們說的是首次創(chuàng)建代理類的情況格嗅,現(xiàn)在我們回頭來看下WeakCache的get方法番挺,如果之前已經(jīng)為類加載器loader和接口interfaces創(chuàng)建過了代理類,那么調(diào)用這個方法的時候是個什么樣子呢屯掖?答案就是根據(jù)一級鍵和二級鍵直接從緩存中取到代理類的Class實例玄柏。這里就不再逐行分析代碼了,讀者自己理解下贴铜。最后用一張簡單概要的時序圖描繪一下Proxy的getProxyClass0方法生成代理類的調(diào)用流程粪摘。

時序圖

2.newProxyInstance

有了上面對動態(tài)代理類的創(chuàng)建過程的系統(tǒng)理解瀑晒,現(xiàn)在來看newProxyInstance方法就容易多了,它就是使用反射機(jī)制調(diào)用動態(tài)代理類的構(gòu)造函數(shù)生成一個代理類實例的過程徘意。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException {
    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});
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

第2-7行代碼苔悦,克隆interfaces,并進(jìn)行權(quán)限相關(guān)校驗映砖,和前面getProxyClass方法類似间坐,這里不做贅述。
第8行g(shù)etProxyClass0方法獲取代理類的Class實例邑退,這個方法在上面也詳細(xì)介紹過了竹宋。
第10-12行也是進(jìn)行權(quán)限校驗,檢查調(diào)用者是否對這個代理類有訪問權(quán)限地技。
第13-23行就是構(gòu)造代理類實例的過程蜈七。先獲取代理類的構(gòu)造函數(shù),接著對其訪問權(quán)限進(jìn)行判斷莫矗,如果不是public飒硅,則將其設(shè)置可訪問的。最后利用反射機(jī)制調(diào)用構(gòu)造方法作谚,傳入?yún)?shù)InvocationHandler的實例h三娩,創(chuàng)建代理類實例并返回。

3.isProxyClass

這個方法用于判斷一個類是不是代理類妹懒。

public static boolean isProxyClass(Class<?> cl) {
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

Proxy.class.isAssignableFrom(cl)這句話簡而言之就是判斷cl所表示的類是不是Proxy或者Proxy的子類雀监。因為所有的代理類都集成Proxy,所以這個條件必須滿足眨唬。滿足這個條件也不能保證就是代理類会前,因為可能存在人為地編寫一個類繼承Proxy這種情況。proxyClassCache.containsValue(cl)這個方法是檢查緩存中是否存在這個Class實例cl匾竿。我前面分析過瓦宜,但凡生成的代理類都會被緩存,所以這個方法才是檢測一個類是否是代理類的唯一標(biāo)準(zhǔn)岭妖。

4.getInvocationHandler

這個方法用于獲取代理類中的InvocationHandler實例临庇。這個方法沒有什么太多的邏輯,基本就是判斷下傳入的對象是否是代理類昵慌,以及一些訪問權(quán)限的校驗苔巨。當(dāng)這些都合法的情況下,返回InvocationHandler實例废离。

public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
    if (!isProxyClass(proxy.getClass())) {
        throw new IllegalArgumentException("not a proxy instance");
    }
    final Proxy p = (Proxy) proxy;
    final InvocationHandler ih = p.h;
    if (System.getSecurityManager() != null) {
        Class<?> ihClass = ih.getClass();
        Class<?> caller = Reflection.getCallerClass();
        if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),ihClass.getClassLoader())) {
            ReflectUtil.checkPackageAccess(ihClass);
        }
    }
    return ih;
}

至此侄泽,關(guān)于JDK動態(tài)代理的技術(shù)都全部講解完了。本文從基本概念蜻韭、樣例分析悼尾、源碼分析三個角度去分析說明JDK動態(tài)代理背后的知識柿扣,希望對讀者有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闺魏,一起剝皮案震驚了整個濱河市未状,隨后出現(xiàn)的幾起案子叁熔,更是在濱河造成了極大的恐慌泵督,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兜喻,死亡現(xiàn)場離奇詭異泡仗,居然都是意外死亡埋虹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門娩怎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搔课,“玉大人,你說我怎么就攤上這事截亦∨滥啵” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵崩瓤,是天一觀的道長袍啡。 經(jīng)常有香客問我,道長却桶,這世上最難降的妖魔是什么境输? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮肾扰,結(jié)果婚禮上畴嘶,老公的妹妹穿的比我還像新娘蛋逾。我一直安慰自己集晚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布区匣。 她就那樣靜靜地躺著偷拔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亏钩。 梳的紋絲不亂的頭發(fā)上莲绰,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音姑丑,去河邊找鬼蛤签。 笑死,一個胖子當(dāng)著我的面吹牛栅哀,可吹牛的內(nèi)容都是我干的震肮。 我是一名探鬼主播称龙,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼戳晌!你這毒婦竟也來了鲫尊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤沦偎,失蹤者是張志新(化名)和其女友劉穎疫向,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豪嚎,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡搔驼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疙渣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匙奴。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妄荔,靈堂內(nèi)的尸體忽然破棺而出泼菌,到底是詐尸還是另有隱情,我是刑警寧澤啦租,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布哗伯,位于F島的核電站,受9級特大地震影響篷角,放射性物質(zhì)發(fā)生泄漏焊刹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一恳蹲、第九天 我趴在偏房一處隱蔽的房頂上張望虐块。 院中可真熱鬧,春花似錦嘉蕾、人聲如沸贺奠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儡率。三九已至,卻和暖如春以清,著一層夾襖步出監(jiān)牢的瞬間儿普,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工掷倔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留眉孩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像浪汪,于是被迫代替她去往敵國和親障贸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容