我準(zhǔn)備戰(zhàn)斗到最后,不是因?yàn)槲矣赂?,是我想?jiàn)證一切。 --雙雪濤《獵人》
[TOC]
Thinking
- 一個(gè)技術(shù),為什么要用它,解決了那些問(wèn)題?
- 如果不用會(huì)怎么樣,有沒(méi)有其它的解決方法?
- 對(duì)比其它的解決方案,為什么最終選擇了這種,都有何利弊?
- 你覺(jué)得項(xiàng)目中還有那些地方可以用到,如果用了會(huì)帶來(lái)那些問(wèn)題?
- 這些問(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):
- 動(dòng)態(tài)代理到底代理了那個(gè)類(lèi)?
- JDK動(dòng)態(tài)代理為什么只針對(duì)接口呢?
- 動(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);
- Subject subject = (Subject) Proxy.newProxyInstance(aClass.getClassLoader(),
2、深入源碼
這里建議使用debug 一步一步跟進(jìn)!?? let·s go
- 進(jìn)入到
java.lang.reflect.Proxy#newProxyInstance

明確一下傳入的參數(shù)的值,以免后面忘記了。
-
然后會(huì)經(jīng)過(guò)一系列的檢測(cè)和接口clone和安全檢測(cè)
- image-20200410205947048
-
下面就是重點(diǎn)了,進(jìn)入到了真正的查找或者創(chuàng)建代理對(duì)象的方法
- image-20200410210052797
-
進(jìn)入該方法后,會(huì)嘗試在緩存中獲取代理類(lèi)對(duì)象(如果存在)
- image-20200410211012222
-
在源碼中我們發(fā)現(xiàn)了調(diào)用了全局變量
proxyClassCache的get方法,先看一下proxyClassCache都做了些什么?- image-20200410211316918
-
創(chuàng)建了一個(gè)
java.lang.reflect.WeakCache類(lèi),并且傳入了兩個(gè)實(shí)例對(duì)象,再逐步跟進(jìn)去查看一下這三個(gè)實(shí)例的初始化過(guò)程。- image-20200410211536301
- 大致情況就是創(chuàng)建一個(gè)密鑰!
- image-20200410211738397
- 上述的兩個(gè)工廠對(duì)象都是實(shí)現(xiàn)了函數(shù)式接口
java.util.function.BiFunction,所以后續(xù)都會(huì)有相應(yīng)的java.util.function.BiFunction#apply方法調(diào)用。
-
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()生成的。
- 這里重點(diǎn)說(shuō)一下
-
再進(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)體
-
image-20200410215823350
- image-20200410220225798
- 此時(shí)的邏輯執(zhí)行完畢,再次進(jìn)入循環(huán)體中,此時(shí)的supplier 則不為空。
- image-20200410220403891
-
image-20200410220803598
- 這里的
valueFactory在類(lèi)初始化時(shí),就已經(jīng)賦值成功了,為:java.lang.reflect.Proxy.ProxyClassFactory,接下來(lái)進(jìn)入真正的創(chuàng)建代理類(lèi)的邏輯了。
- 這里的
-
在進(jìn)行完一系列的檢測(cè)和數(shù)據(jù)拼接后,終于到了最終構(gòu)建代理類(lèi)實(shí)例的時(shí)候了
- image-20200410221147821
- image-20200410221216203
-
image-20200410221310586
- 這里方法是具體的構(gòu)建代理類(lèi)對(duì)象中的所有字節(jié)碼結(jié)構(gòu),包括(常量池、字段、方法、等),進(jìn)去瞄一眼?。?!
-
首先看到該類(lèi)下有一個(gè)靜態(tài)的代碼塊,對(duì)Object類(lèi)下的hashCode、toString、equals三個(gè)放進(jìn)行封裝image-20200410221609074
- 將這三個(gè)方法構(gòu)建到代理類(lèi)中,并且會(huì)保持在代理類(lèi)的真實(shí)方法之前。
- image-20200410221745030
- 將代理類(lèi)中的所有方法添加到構(gòu)建中
- image-20200410221839172
- 后面的一系列操作都是對(duì)代理類(lèi)的二進(jìn)制文件進(jìn)行構(gòu)建。有興趣可以看一下源碼
- image-20200410221934935
-
代理對(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
-
在生成二進(jìn)制文件之后,會(huì)調(diào)用image-20200410222621477
調(diào)用本地方法生成具體的代理對(duì)象,根據(jù)制定的類(lèi)加載器,類(lèi)名,二進(jìn)制文件
然后將生成的代理對(duì)象放入到cache中等一系列的操作在這里就不贅述了。
- 再回到
java.lang.reflect.Proxy#newProxyInstance中image-20200410222930327- 會(huì)通過(guò)class 文件獲取構(gòu)造函數(shù)
- image-20200410223340825
-
參數(shù)類(lèi)型為:image-20200410223405623
-
然后將傳入的handle 當(dāng)作參數(shù)進(jìn)行對(duì)象的初始化image-20200410223501823
- 這里使用對(duì)象數(shù)組,是因?yàn)楂@取的構(gòu)造函數(shù)是使用InvocationHandler數(shù)據(jù)獲取到的。
- 至此:代理對(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

明確的指到:在使用沒(méi)有重寫(xiě)的方法會(huì)直接調(diào)用Object類(lèi)的方法,跟平時(shí)無(wú)異
4、再看字節(jié)碼文件
? 在添加系統(tǒng)屬性之后,會(huì)生成代理類(lèi)的class 文件。來(lái)看一下具體的class文件

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

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

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


所以這里就更加清楚的知道在class文件中每一個(gè)方法都是使用super.h.invoke的方式進(jìn)行調(diào)用的。
這里看一下程序的啟動(dòng)類(lèi),具體是怎么定義的。

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

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

圖中的疑問(wèn),一樣也非常好解答
就是在啟動(dòng)類(lèi)中,在初始化DynamicSubject時(shí),定義了sub的屬性。

所以在調(diào)用invoke方法時(shí),會(huì)根據(jù)具體的實(shí)例對(duì)象進(jìn)行調(diào)用。
所以現(xiàn)在應(yīng)該非常明確的知道在動(dòng)態(tài)代理時(shí),所有的執(zhí)行流程了

5、疑問(wèn)清除術(shù)
- 動(dòng)態(tài)代理到底代理了那個(gè)類(lèi)?
- JDK動(dòng)態(tài)代理為什么只針對(duì)接口呢?
- 動(dòng)態(tài)代理到底是什么時(shí)候?qū)㈩?lèi)創(chuàng)建出來(lái)的?又是如何創(chuàng)建的?創(chuàng)建的具體是哪個(gè)類(lèi)對(duì)象呢?
- 動(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)。
- JDK 源碼中只會(huì)根據(jù)接口數(shù)組進(jìn)行對(duì)數(shù)據(jù)的構(gòu)建。并不支持繼承等其它形式
- 動(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ā)布!






























