JVM-從字節(jié)碼角度深入探討JDK動(dòng)態(tài)代理


我準(zhǔn)備戰(zhàn)斗到最后,不是因?yàn)槲矣赂?,是我想?jiàn)證一切。 --雙雪濤《獵人》

[TOC]
Thinking

  1. 一個(gè)技術(shù),為什么要用它,解決了那些問(wèn)題?
  2. 如果不用會(huì)怎么樣,有沒(méi)有其它的解決方法?
  3. 對(duì)比其它的解決方案,為什么最終選擇了這種,都有何利弊?
  4. 你覺(jué)得項(xiàng)目中還有那些地方可以用到,如果用了會(huì)帶來(lái)那些問(wèn)題?
  5. 這些問(wèn)題你又如何去解決的呢?

思考

? Java是一個(gè)強(qiáng)類(lèi)型語(yǔ)言,而Java提供的編譯期和運(yùn)行期加載的機(jī)制,讓Java更加靈活的塑造自己。其中動(dòng)態(tài)加載可以說(shuō)是Java生態(tài)中非常重要的一環(huán)。

? 提到動(dòng)態(tài)代理,應(yīng)該大多數(shù)人都會(huì)第一時(shí)間想到spring提供的AOP。它就是通過(guò)動(dòng)態(tài)代理在JVM運(yùn)行期動(dòng)態(tài)編織再通過(guò)依賴(lài)注入,將動(dòng)態(tài)代理出的真實(shí)對(duì)象注入到對(duì)應(yīng)的類(lèi)中??上攵?,動(dòng)態(tài)代理在spring中的重要地位。

詳情可以參讀spring aop 的實(shí)現(xiàn)原理

首先提出幾個(gè)疑問(wèn):

  1. 動(dòng)態(tài)代理到底代理了那個(gè)類(lèi)?
  2. JDK動(dòng)態(tài)代理為什么只針對(duì)接口呢?
  3. 動(dòng)態(tài)代理到底是什么時(shí)候?qū)㈩?lèi)創(chuàng)建出來(lái)的?又是如何創(chuàng)建的?創(chuàng)建的具體是哪個(gè)類(lèi)對(duì)象呢?

帶著疑問(wèn),往下走??

1、編寫(xiě)一個(gè)JDK動(dòng)態(tài)代理

public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is running");
    }
}

public class DynamicSubject implements InvocationHandler {
    // 將真實(shí)的對(duì)象 作為成員變量 通過(guò)構(gòu)造方法傳入進(jìn)來(lái)
    private Object sub;

    public DynamicSubject(Object sub) {
        this.sub = sub;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before calling: " + method);
        method.invoke(this.sub, args);
        System.out.println("after calling: " + method);

        return null;
    }
}

// Test
public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        InvocationHandler handler = new DynamicSubject(realSubject);
        Class<? extends RealSubject> aClass = realSubject.getClass();

        // 真正的 類(lèi)型則是動(dòng)態(tài)綁定的
        Subject subject = (Subject) Proxy.newProxyInstance(aClass.getClassLoader(),
                aClass.getInterfaces(), handler);
        subject.request();
        System.out.println(subject.getClass());
        System.out.println(subject.getClass().getSuperclass());
        // class com.sun.proxy.$Proxy0
        // class java.lang.reflect.Proxy
    }
}
  • 從上面的 代碼可以看到真正的代理類(lèi)的創(chuàng)建代碼為:
    • Subject subject = (Subject) Proxy.newProxyInstance(aClass.getClassLoader(),
      aClass.getInterfaces(), handler);

2、深入源碼

這里建議使用debug 一步一步跟進(jìn)!?? let·s go

  1. 進(jìn)入到java.lang.reflect.Proxy#newProxyInstance
image-20200410205747052

明確一下傳入的參數(shù)的值,以免后面忘記了。

  1. 然后會(huì)經(jīng)過(guò)一系列的檢測(cè)和接口clone和安全檢測(cè)

    1. image-20200410205947048
  2. 下面就是重點(diǎn)了,進(jìn)入到了真正的查找或者創(chuàng)建代理對(duì)象的方法

    1. image-20200410210052797
  3. 進(jìn)入該方法后,會(huì)嘗試在緩存中獲取代理類(lèi)對(duì)象(如果存在)

    1. image-20200410211012222
    2. 在源碼中我們發(fā)現(xiàn)了調(diào)用了全局變量proxyClassCache的get方法,先看一下proxyClassCache都做了些什么?

      1. image-20200410211316918
      2. 創(chuàng)建了一個(gè)java.lang.reflect.WeakCache類(lèi),并且傳入了兩個(gè)實(shí)例對(duì)象,再逐步跟進(jìn)去查看一下這三個(gè)實(shí)例的初始化過(guò)程。

        1. image-20200410211536301
        • 大致情況就是創(chuàng)建一個(gè)密鑰!
        1. image-20200410211738397
        • 上述的兩個(gè)工廠對(duì)象都是實(shí)現(xiàn)了函數(shù)式接口java.util.function.BiFunction,所以后續(xù)都會(huì)有相應(yīng)的java.util.function.BiFunction#apply方法調(diào)用。
        1. image-20200410212539975
          • 這里重點(diǎn)說(shuō)一下java.lang.reflect.WeakCache中的map變量,其中map變量是實(shí)現(xiàn)緩存的核心變量,他是一個(gè)雙重的Map結(jié)構(gòu): (key, sub-key) -> value。其中key是傳進(jìn)來(lái)的Classloader進(jìn)行包裝后的對(duì)象,sub-key是由WeakCache構(gòu)造函數(shù)傳人的KeyFactory()生成的。value就是產(chǎn)生代理類(lèi)的對(duì)象,是由WeakCache構(gòu)造函數(shù)傳人的ProxyClassFactory()生成的。
  4. 再進(jìn)入到生成的java.lang.reflect.WeakCache的get方法。

    • 該方法會(huì)嘗試在緩存中獲取,或者自己根據(jù)類(lèi)加載器和接口數(shù)組創(chuàng)建代理對(duì)象(緩存獲取不到的情況下)
    • image-20200410215338299
    • image-20200410215524436
    • 調(diào)用KeyFacroty 對(duì)象中的apply方法,生成密鑰
      • image-20200410215627655
      • 在上面的一系列操作后,發(fā)現(xiàn)在緩存中獲取不到數(shù)據(jù),會(huì)進(jìn)入一個(gè)while循環(huán)體
  5. image-20200410215823350
    • image-20200410220225798
    • 此時(shí)的邏輯執(zhí)行完畢,再次進(jìn)入循環(huán)體中,此時(shí)的supplier 則不為空。
  6. image-20200410220403891
  7. image-20200410220803598
    • 這里的valueFactory在類(lèi)初始化時(shí),就已經(jīng)賦值成功了,為:java.lang.reflect.Proxy.ProxyClassFactory,接下來(lái)進(jìn)入真正的創(chuàng)建代理類(lèi)的邏輯了。
  8. 在進(jìn)行完一系列的檢測(cè)和數(shù)據(jù)拼接后,終于到了最終構(gòu)建代理類(lèi)實(shí)例的時(shí)候了

    1. image-20200410221147821
    2. image-20200410221216203
    3. image-20200410221310586
      1. 這里方法是具體的構(gòu)建代理類(lèi)對(duì)象中的所有字節(jié)碼結(jié)構(gòu),包括(常量池、字段、方法、等),進(jìn)去瞄一眼?。?!
      2. 首先看到該類(lèi)下有一個(gè)靜態(tài)的代碼塊,對(duì)Object類(lèi)下的hashCode、toString、equals三個(gè)放進(jìn)行封裝
        image-20200410221609074
      3. 將這三個(gè)方法構(gòu)建到代理類(lèi)中,并且會(huì)保持在代理類(lèi)的真實(shí)方法之前。
      4. image-20200410221745030
      5. 將代理類(lèi)中的所有方法添加到構(gòu)建中
      6. image-20200410221839172
      7. 后面的一系列操作都是對(duì)代理類(lèi)的二進(jìn)制文件進(jìn)行構(gòu)建。有興趣可以看一下源碼
      8. image-20200410221934935
  9. 代理對(duì)象的二進(jìn)制文件構(gòu)建完之后會(huì)有一個(gè)判斷

    • image-20200410222058627
    • 這個(gè)判斷既是系統(tǒng)屬性,在開(kāi)啟之后,會(huì)將生成的代理對(duì)象的二進(jìn)制文件輸出到磁盤(pán)中
    • image-20200410222203254
    • 具體操作如下:
      • image-20200410222227455
      • debug運(yùn)行后會(huì)得到一個(gè)$Proxy0.class文件在項(xiàng)目的根路徑
      • image-20200410222329274
  10. 在生成二進(jìn)制文件之后,會(huì)調(diào)用
    image-20200410222621477

    調(diào)用本地方法生成具體的代理對(duì)象,根據(jù)制定的類(lèi)加載器,類(lèi)名,二進(jìn)制文件

  11. 然后將生成的代理對(duì)象放入到cache中等一系列的操作在這里就不贅述了。


  1. 再回到java.lang.reflect.Proxy#newProxyInstance
    image-20200410222930327
    1. 會(huì)通過(guò)class 文件獲取構(gòu)造函數(shù)
    2. image-20200410223340825
    3. 參數(shù)類(lèi)型為:
      image-20200410223405623
    4. 然后將傳入的handle 當(dāng)作參數(shù)進(jìn)行對(duì)象的初始化
      image-20200410223501823
      1. 這里使用對(duì)象數(shù)組,是因?yàn)楂@取的構(gòu)造函數(shù)是使用InvocationHandler數(shù)據(jù)獲取到的。
  2. 至此:代理對(duì)象就創(chuàng)建完畢了。很簡(jiǎn)單對(duì)不對(duì)??

3、通過(guò)上述的源碼學(xué)習(xí)到了

- 在源碼中使用到了JDK8的函數(shù)式編程,所以代碼邏輯可能比較繞。但是讀下來(lái)還是很輕松的。

- 在讀到源碼的數(shù)據(jù)構(gòu)建中,明白了具體的構(gòu)建參數(shù)

? 在代理類(lèi)的構(gòu)建中,會(huì)在真的是代理類(lèi)的方法之前加入Object中的三個(gè)方法。保證了在代理類(lèi)中使用到hashCode、toString、equals時(shí),會(huì)直接使用代理類(lèi)中的重寫(xiě)方法,但是如果用到其它的方法例如clone等等的方法呢?

? 查看一下JAVADOC

image-20200410224325790

明確的指到:在使用沒(méi)有重寫(xiě)的方法會(huì)直接調(diào)用Object類(lèi)的方法,跟平時(shí)無(wú)異

4、再看字節(jié)碼文件

? 在添加系統(tǒng)屬性之后,會(huì)生成代理類(lèi)的class 文件。來(lái)看一下具體的class文件

image-20200410224608268

首先看到有四個(gè)變量。都為Method類(lèi)型。也很好理解。method可以通過(guò)反射直接調(diào)用指定的方法。

image-20200410224718908

再看有一個(gè)靜態(tài)的代碼塊。用于對(duì)成員變量的初始化??梢院芮宄目吹缴傻木唧w方法,也很好的對(duì)應(yīng)了源碼中的邏輯。

image-20200410224917342

再看構(gòu)造函數(shù),可以發(fā)現(xiàn)啊,在源碼中使用構(gòu)造函數(shù)調(diào)用時(shí)會(huì)指定啟動(dòng)程序指定的InvocationHandler參數(shù),在這里會(huì)初始化到父類(lèi)的成員變量中

image-20200410225051984

image-20200410225108876

所以這里就更加清楚的知道在class文件中每一個(gè)方法都是使用super.h.invoke的方式進(jìn)行調(diào)用的。


這里看一下程序的啟動(dòng)類(lèi),具體是怎么定義的。

image-20200410225330594

所以不難看出這里初始化的h是指向啟動(dòng)程序中的DynamicSubject

image-20200410225459682

再看request方法

? 我們?cè)倏瓷傻拇眍?lèi)中的request方法。

image-20200410230158395

圖中的疑問(wèn),一樣也非常好解答

就是在啟動(dòng)類(lèi)中,在初始化DynamicSubject時(shí),定義了sub的屬性。

image-20200410230347371

所以在調(diào)用invoke方法時(shí),會(huì)根據(jù)具體的實(shí)例對(duì)象進(jìn)行調(diào)用。

所以現(xiàn)在應(yīng)該非常明確的知道在動(dòng)態(tài)代理時(shí),所有的執(zhí)行流程了

image-20200410230705690

5、疑問(wèn)清除術(shù)

  1. 動(dòng)態(tài)代理到底代理了那個(gè)類(lèi)?
  2. JDK動(dòng)態(tài)代理為什么只針對(duì)接口呢?
  3. 動(dòng)態(tài)代理到底是什么時(shí)候?qū)㈩?lèi)創(chuàng)建出來(lái)的?又是如何創(chuàng)建的?創(chuàng)建的具體是哪個(gè)類(lèi)對(duì)象呢?
  1. 動(dòng)態(tài)代理其實(shí)是根據(jù)給定的類(lèi)加載器和類(lèi)的接口數(shù)組,在Java運(yùn)行期由JDK自行創(chuàng)建的一個(gè)類(lèi),用于執(zhí)行一些相關(guān)邏輯,這種設(shè)計(jì)極大程度的豐富了Java的設(shè)計(jì)理念,和動(dòng)態(tài)加載的豐富性。例如:spring AOP。在程序運(yùn)行時(shí)對(duì)程序進(jìn)行增強(qiáng)。
  2. JDK 源碼中只會(huì)根據(jù)接口數(shù)組進(jìn)行對(duì)數(shù)據(jù)的構(gòu)建。并不支持繼承等其它形式
  3. 動(dòng)態(tài)代理是在程序運(yùn)行時(shí)將類(lèi)創(chuàng)建出來(lái)的,可以最大程序的將程序變得更加靈活。會(huì)根據(jù)具體指定的接口對(duì)象進(jìn)行具體操作和實(shí)現(xiàn)。

6、TODO

? JDK的動(dòng)態(tài)代理是有局限的,所以在spring中已經(jīng)集成了cjlib基于繼承的方式進(jìn)行動(dòng)態(tài)代理。有空再說(shuō)咯 ??

本文應(yīng)該存在大量的錯(cuò)誤,因?yàn)槎际枪P者自行理解做出的總結(jié),僅供個(gè)人學(xué)習(xí)備忘。

本文僅供筆者本人學(xué)習(xí),有錯(cuò)誤的地方還望指出,一起進(jìn)步!望海涵!

轉(zhuǎn)載請(qǐng)注明出處!

歡迎關(guān)注我的公共號(hào),無(wú)廣告,不打擾。不定時(shí)更新Java后端知識(shí),我們一起超神。


qrcode.jpg

——努力努力再努力xLg

加油!
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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