Java動(dòng)態(tài)代理模式的深入揭秘
前言
👉本文中所有的代碼和運(yùn)行結(jié)果都是在amazon corretto openjdk 1.8環(huán)境中的,如果你不是使用該環(huán)境,可能會(huì)略有偏差。另外為了代碼看起來清晰整潔,將所有代碼中的異常處理邏輯全部拿去了。
一些廢話
哈嘍,各位讀者您們好,好久不見!距離上一篇我寫的文章已經(jīng)半個(gè)月有余,沒辦法,我也是菜鳥一枚,而且我寫文章有原則,每一篇都必須要醞釀得夠深刻,高質(zhì)量,能夠直擊靈魂深處......如果只是淺嘗輒止我寧可不浪費(fèi)這時(shí)間,而且有些內(nèi)容我也不會(huì)正在學(xué)習(xí)中,所以我輸出的頻率必然是低的,但是質(zhì)量必然是高的。😎不廢話,下面開始我們今天的主題。
今天我要跟大家聊的是Java當(dāng)中的動(dòng)態(tài)代理模式。相信每一個(gè)學(xué)過Java的朋友,只要是對GOF23設(shè)計(jì)模式有簡單了解過的,或者看過我github上面以前學(xué)習(xí)時(shí)記的筆記,或多或少是聽說過代理模式的。這一模式可以說是GOF23所有設(shè)計(jì)模式中應(yīng)用最廣泛,但又最難以理解的一種模式,尤其是其中的動(dòng)態(tài)代理模式,但是其功能之強(qiáng)大,應(yīng)用場景之廣自然就體現(xiàn)出其重要性。有些場景要是沒有使用這一模式,就會(huì)變得很難實(shí)現(xiàn)??梢赃@么說,我所了解過的或者閱讀過源碼的開源框架,底層幾乎沒有不用到代理模式的,尤其是接下去本文要說的重點(diǎn)-動(dòng)態(tài)代理模式。因此,在文章的最后,我也會(huì)以一個(gè)在mybatis底層使用動(dòng)態(tài)代理模式解決的經(jīng)典場景作為本文結(jié)束。
代理
首先,我們先來說說代理。何為代理?來看張圖。這就是我們?nèi)粘W夥康膱鼍?,客戶來一個(gè)陌生城市需要租一個(gè)房子,但是他人生地不熟,根本不知道行情,也不知道地段,更沒有房東的聯(lián)系方式,所以,他會(huì)去找類似我愛我家之類的租房中介,而這些個(gè)中介手上會(huì)有大量房子的信息來源,自然會(huì)有個(gè)房東的聯(lián)系方式,進(jìn)而和房東取得聯(lián)系,從而達(dá)到租房的目的。這個(gè)場景就是一個(gè)經(jīng)典的代理模式的體現(xiàn)。

靜態(tài)代理
既然說到動(dòng)態(tài)代理,自然聯(lián)想到肯定會(huì)有靜態(tài)代理。下面我們就先從簡單的開始,以上面租房的這個(gè)例子,用Java代碼實(shí)現(xiàn)靜態(tài)代理。
首先在代理模式(甭管靜態(tài)還是動(dòng)態(tài))結(jié)構(gòu)中,肯定會(huì)有一個(gè)真實(shí)角色(Target),也是最后真正執(zhí)行業(yè)務(wù)邏輯的那個(gè)對象,比如上圖中的房東(因?yàn)樽詈笞獾姆孔铀袡?quán)是他的,也是和他去辦租房合同等手續(xù)),另外會(huì)有一個(gè)代理角色(Proxy),比如上圖中的房產(chǎn)中介(他沒有房產(chǎn)所有權(quán)),并且這個(gè)角色會(huì)必然實(shí)現(xiàn)一個(gè)與真實(shí)角色相同的抽象接口(Subject),為什么呢?因?yàn)殡m然這個(gè)出租的房子不是他的,但是是經(jīng)他之手幫忙牽線搭橋出租出去的,也就是說,他和房東都會(huì)有出租房產(chǎn)的行為。另外代理角色會(huì)持有一個(gè)真實(shí)角色的引用,又是為什么呢?因?yàn)樗⒉粫?huì)(或者是不能)真正處理業(yè)務(wù)邏輯(因?yàn)榉孔硬皇撬膯h),他會(huì)將真正的邏輯委托給真實(shí)角色處理。但是這個(gè)代理角色也不是一無是處,除了房子不是他的,但是他還可以給你干點(diǎn)跑腿的工作嘛,比如幫你挑選最好的地段,挑選合適的價(jià)格等等,等你租房后出現(xiàn)漏水,或者電器啥的壞了可以幫你聯(lián)系維修人員等等。如下代碼所示:
//公共抽象接口 - 出租的人
public interface Person {
void rent();
}
//真實(shí)角色 - 房東
public class Landlord implements Person{
public void rent() {
System.out.println("客官請進(jìn),我家的房子又大又便宜,來租我的吧...");
}
}
//代理角色 - 房產(chǎn)中介
public class Agent implements Person{
Person landlord;
public Agent(Person landlord) {
this.landlord = landlord;
}
public void rent() {
//前置處理
System.out.println("經(jīng)過前期調(diào)研,西湖邊的房子環(huán)境挺好的...");
//委托真實(shí)角色處理
landlord.rent();
//后置處理
System.out.println("房子漏水,幫你聯(lián)系維修人員...");
}
}
//客戶端
public class Client {
public static void main(String[] args) {
Person landlord = new Landlord();
Person agent = new Agent(landlord);
agent.rent();
}
}
//輸出結(jié)果:
經(jīng)過前期調(diào)研,西湖邊的房子環(huán)境挺好的...
客官請進(jìn),我家的房子又大又便宜,來租我的吧...
房子漏水,幫你聯(lián)系維修人員...
靜態(tài)代理模式實(shí)現(xiàn)相對比較簡單,而且比較好理解,也確實(shí)實(shí)現(xiàn)了代理的效果。但是很遺憾,幾乎沒有一個(gè)開源框架的內(nèi)部是采用靜態(tài)代理來實(shí)現(xiàn)代理模式的。那是為什么呢?原因很簡單,從上面這個(gè)例子可以看出,靜態(tài)代理模式中的真實(shí)角色和代理角色緊耦合了。怎么理解?
下面來舉個(gè)例子幫助理解靜態(tài)代理模式的缺點(diǎn),深入理解靜態(tài)代理的缺點(diǎn)對于理解動(dòng)態(tài)代理的應(yīng)用場景是至關(guān)重要的。因?yàn)閯?dòng)態(tài)代理的誕生就是為了解決這一問題。
還是以上面的租房的場景,假設(shè)我現(xiàn)在需要你實(shí)現(xiàn)如下需求:有多個(gè)房東,并且每個(gè)房東都有多套房子出租,你怎么用Java設(shè)計(jì)?按照上面的靜態(tài)代理模式的思路,你也許會(huì)有如下實(shí)現(xiàn)(偽代碼),
第一種方案:
public class Landlord01 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
public class Landlord02 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
public class Landlord03 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
... 可能還有很多房東,省略
public class Agent01 implements Person{
Person landlord01;
//省略構(gòu)造器等信息
public void rent() {landlord01.rent();}
}
public class Agent02 implements Person{
Person landlord02;
//省略構(gòu)造器等信息
public void rent() {landlord02.rent();}
}
public class Agent03 implements Person{
Person landlord03;
//省略構(gòu)造器等信息
public void rent() {landlord03.rent();}
}
...
上面這種方案是為每個(gè)房東配一個(gè)對應(yīng)的中介處理租房相關(guān)事宜。這種方案問題非常明顯,每一個(gè)真實(shí)角色都需要手動(dòng)創(chuàng)建一個(gè)代理角色與之對應(yīng),而這些代理類的邏輯有可能都是很相似的,因此當(dāng)真實(shí)角色數(shù)量非常多時(shí),會(huì)造成代理類數(shù)量膨脹問題和代碼重復(fù)冗余,方案不可取。
第二種方案:
public class Landlord01 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
public class Landlord02 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
public class Landlord03 implements Person{
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
public class Agent implements Person{
Person landlord01;
Person landlord02;
Person landlord03;
//省略構(gòu)造器等信息
public void rent01() { ... }
public void rent02() { ... }
public void rent03() { ... }
}
第二種方案只創(chuàng)建一個(gè)代理角色,同時(shí)代理多個(gè)真實(shí)角色,這看上去貌似解決了第一種方案的弊病,但是同時(shí)引入了新的問題。那就是造成了代理類的膨脹。設(shè)計(jì)模式中有條重要原則——單一職責(zé)原則。這個(gè)代理類違反了該原則。當(dāng)這個(gè)代理類為了代理其中某個(gè)真實(shí)角色時(shí),需要將所有的真實(shí)角色的引用全部傳入,顯然太不靈活了。還是不可取。
而且有沒有發(fā)現(xiàn)靜態(tài)代理還有兩個(gè)很大的問題,第一,當(dāng)抽象接口一旦修改,真實(shí)角色和代理角色必須全部做修改,這違反了設(shè)計(jì)模式的開閉原則。第二,每次創(chuàng)建一個(gè)代理角色,需要手動(dòng)傳入一個(gè)已經(jīng)存在的真實(shí)角色。但是在有些場景下,我們可能需要在并不知道真實(shí)角色的情況下創(chuàng)建出指定接口的代理。
動(dòng)態(tài)代理
前面做了這么多鋪墊,終于今天本文的主角——?jiǎng)討B(tài)代理模式要登場了。此處應(yīng)該有掌聲......👏而動(dòng)態(tài)代理模式的產(chǎn)生就是為了解決上面提到的靜態(tài)代理所有弊病的。
JDK動(dòng)態(tài)代理的實(shí)現(xiàn)關(guān)鍵在于java.lang.reflect.Proxy類,其newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)方法是整個(gè)JDK動(dòng)態(tài)代理的核心,用于生成指定接口的代理對象。這個(gè)方法有三個(gè)參數(shù),分別表示加載動(dòng)態(tài)生成的代理類的類加載器ClassLoader,代理類需要實(shí)現(xiàn)的接口interfaces以及調(diào)用處理器InvocationHandler,這三個(gè)參數(shù)一個(gè)比一個(gè)難以理解,說實(shí)話,我第一次學(xué)動(dòng)態(tài)代理模式時(shí),看到這三個(gè)參數(shù)也是一臉懵逼的狀態(tài)。動(dòng)態(tài)代理模式之所以比較難理解關(guān)鍵也是這個(gè)原因。放心,后面會(huì)一一詳解。但在這之前,我們先做一下熱身,先用代碼簡單使用一下JDK的動(dòng)態(tài)代理功能。代碼如下:
//公共抽象接口和真實(shí)角色和靜態(tài)代理的例子中代碼相同,省略
//自定義調(diào)用處理器
public class RentHandler implements InvocationHandler {
Person landlord;
public RentHandler(Person landlord) {
this.landlord = landlord;
}
//客戶端對代理對象發(fā)起的所有請求都會(huì)被委托給該方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置處理
System.out.println("經(jīng)過前期調(diào)研,西湖邊的房子環(huán)境挺好的...");
//委托給真實(shí)角色處理業(yè)務(wù)邏輯
method.invoke(landlord, args);
//后置處理
System.out.println("房子漏水,幫你聯(lián)系維修人員...");
return null;
}
}
//客戶端
public class Client2 {
public static void main(String[] args) {
Person landlord = new Landlord();
Person proxy = (Person) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), //加載代理類的類加載器
new Class[]{Person.class}, //代理的接口
new RentHandler(landlord));//自定義調(diào)用處理器實(shí)現(xiàn)
proxy.rent();
}
}
//輸出結(jié)果:
經(jīng)過前期調(diào)研,西湖邊的房子環(huán)境挺好的...
客官請進(jìn),我家的房子又大又便宜,來租我的吧...
房子漏水,幫你聯(lián)系維修人員...
可以看出,動(dòng)態(tài)代理輕松的實(shí)現(xiàn)了代理模式,并且輸出了和靜態(tài)代理相同的結(jié)果,然而我們并沒有寫任何的代理類,是不是很神奇?下面我們就來深度剖析JDK實(shí)現(xiàn)的動(dòng)態(tài)代理的原理。
Proxy.newProxyInstance()
在上面實(shí)現(xiàn)的JDK動(dòng)態(tài)代理代碼中,核心的一行代碼就是調(diào)用Proxy.newProxyInstance(),傳入類加載器等參數(shù),然后一頓神奇的操作后居然就直接返回了我們所需要的代理對象,因此我們就從這個(gè)神奇的方法開始說起......
進(jìn)入這個(gè)方法的源碼中,以下是這個(gè)方法的核心代碼,邏輯非常清楚,使用getProxyClass0獲取一個(gè)Class對象,其實(shí)這個(gè)就是最終生成返回的代理代理類的Class對象,然后使用反射方式獲取有參構(gòu)造器,并傳入我們的自定義InvocationHandler實(shí)例創(chuàng)建其對象。由此我們其實(shí)已經(jīng)可以猜測,這個(gè)動(dòng)態(tài)生成的代理類會(huì)有一個(gè)參數(shù)為InvocationHandler的構(gòu)造器,這一點(diǎn)在之后會(huì)得到驗(yàn)證。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
... //省略一些非空校驗(yàn),權(quán)限校驗(yàn)的邏輯
//返回一個(gè)代理類,這個(gè)是整個(gè)方法的核心,后續(xù)會(huì)做詳細(xì)剖析
Class<?> cl = getProxyClass0(loader, intfs);
//使用反射獲取其有參構(gòu)造器,constructorParams是定義在Proxy類中的字段,值為{InvocationHandler.class}
final Constructor<?> cons = cl.getConstructor(constructorParams);
//使用返回創(chuàng)建代理對象
return cons.newInstance(new Object[]{h});
}
那現(xiàn)在很明顯了,關(guān)鍵的核心就在于getProxyClass0()方法的邏輯了,于是我們繼續(xù)深入虎穴查看其源碼。
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
最開始就是檢驗(yàn)一下實(shí)現(xiàn)接口數(shù)量,然后執(zhí)行proxyClassCache.get()。proxyClassCache是一個(gè)定義在Proxy中的字段,你就將其當(dāng)做一個(gè)代理類的緩存。這個(gè)也好理解,稍后大家會(huì)看到,動(dòng)態(tài)代理類生成過程中會(huì)伴隨大量的IO操作,字節(jié)碼操作還有反射操作,還是比較消耗資源的。如果需要?jiǎng)?chuàng)建的代理類數(shù)量特別多,性能會(huì)比較差。所以Proxy提供了緩存機(jī)制,將已經(jīng)生成的代理類緩存,當(dāng)獲取時(shí),會(huì)先從緩存獲取,如果獲取不到再執(zhí)行生成邏輯。
我們繼續(xù)進(jìn)入proxyClassCache.get()。這個(gè)方法看起來比較費(fèi)勁,因?yàn)槲沂褂玫氖荍DK8,這邊用到了大量的Java8新增的函數(shù)式編程的語法和內(nèi)容,因?yàn)檫@邊不是專門講Java8的,所以我就不展開函數(shù)式編程的內(nèi)容了。以后有機(jī)會(huì)在其它專題詳述。另外,這邊會(huì)有很多對緩存的操作,這個(gè)不是我們的重點(diǎn),所以也全部跳過,我們挑重點(diǎn)看,關(guān)注一下下面這部分代碼:
public V get(K key, P parameter){
... //省略大量的緩存操作
while (true) {
if (supplier != null) {
V value = supplier.get();
if (value != null) {
return value; ★
}
}
if (factory == null) {
factory = new WeakCache.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);
}
}
}
}
這個(gè)代碼非常有意思,是一個(gè)死循環(huán)。或許你和我一樣,完全看不懂這代碼是啥意思,沒關(guān)系,可以仔細(xì)觀察一下這代碼你就會(huì)發(fā)現(xiàn)柳暗花明。這個(gè)方法最后會(huì)需要返回一個(gè)從緩存或者新創(chuàng)建的代理類,而這整個(gè)死循環(huán)只有一個(gè)出口,沒錯(cuò)就是帶★這一行,而value是通過supplier.get()獲得,Supplier是一個(gè)函數(shù)式接口,代表了一種數(shù)據(jù)的獲取操作。我們再觀察會(huì)發(fā)現(xiàn),supplier是通過factory賦值而來的。而factory是通過▲行創(chuàng)建出來的。WeakCache.Factory恰好是Supplier的實(shí)現(xiàn)。所以我們進(jìn)入WeakCache.Factory的get(),核心代碼如下,經(jīng)觀察可以發(fā)現(xiàn),返回的數(shù)據(jù)最終是通過valueFactory.apply()返回的。
public synchronized V get() {
... //省略一些緩存操作
V value = null;
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
... //省略一些緩存操作
return value;
}
apply是BiFunction的一個(gè)抽象方法,BiFunction又是一個(gè)函數(shù)式接口。而valueFactory是通過WeakCache的構(gòu)造器傳入,是一個(gè)ProxyClassFactory對象,而其剛好就是BiFunction的實(shí)現(xiàn),顧名思義,這個(gè)類就是專門用來創(chuàng)建代理類的工廠類。

進(jìn)入ProxyClassFactory的apply()方法,代碼如下:
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
//對每一個(gè)指定的Class校驗(yàn)其是否能被指定的類加載器加載以及校驗(yàn)是否是接口,動(dòng)態(tài)代理只能對接口代理,至于原因,后面會(huì)說。
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
interfaceClass = Class.forName(intf.getName(), false, loader);
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());
}
}
//下面這一大段是用來指定生成的代理類的包信息
//如果全是public的,就是用默認(rèn)的com.sun.proxy,
//如果有非public的,所有的非public接口必須處于同一級別包下面,而該包路徑也會(huì)成為生成的代理類的包。
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();
//代理類最后生成的名字是包名+$Proxy+一個(gè)數(shù)字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//生成代理類的核心
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);★
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
通過上面代碼不難發(fā)現(xiàn),生成代理類的核心代碼在★這一行,會(huì)使用一個(gè)ProxyGenerator生成代理類(以byte[]形式存在)。然后將生成得到的字節(jié)數(shù)組轉(zhuǎn)換為一個(gè)Class對象。進(jìn)入ProxyGenerator.generateProxyClass()。ProxyGenerator處于sun.misc包,不是開源的包,因?yàn)槲疫@邊使用的是openjdk,所以可以直接查看其源碼,如果使用的是oracle jdk的話,這邊只能通過反編譯class文件查看。
public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
//省略一堆IO操作
}
return classFile;
}
上述邏輯很簡單,就是使用一個(gè)生成器調(diào)用generateClassFile()方法返回代理類,后面有個(gè)if判斷我簡單提一下,這個(gè)作用主要是將內(nèi)存中動(dòng)態(tài)生成的代理類以class文件形式保存到硬盤。saveGeneratedFiles這個(gè)字段是定義在ProxyGenerator中的字段,
private final static boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
我簡單說一下,AccessController.doPrivileged這個(gè)玩意會(huì)去調(diào)用java.security.PrivilegedAction的run()方法,GetBooleanAction這個(gè)玩意就實(shí)現(xiàn)了java.security.PrivilegedAction,在其run()中會(huì)通過Boolean.getBoolean()從系統(tǒng)屬性中獲取sun.misc.ProxyGenerator.saveGeneratedFiles的值,默認(rèn)是false,如果想要將動(dòng)態(tài)生成的class文件持久化,可以往系統(tǒng)屬性中設(shè)置為true。
我們重點(diǎn)進(jìn)入ProxyGenerator.generateClassFile()方法,代碼如下:
private byte[] generateClassFile() {
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
for (List<ProxyGenerator.ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
methods.add(generateConstructor());
for (List<ProxyGenerator.ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyGenerator.ProxyMethod pm : sigmethods) {
fields.add(new ProxyGenerator.FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf : interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (ProxyGenerator.FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (ProxyGenerator.MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0);
return bout.toByteArray();
}
如果沒有學(xué)過Java虛擬機(jī)規(guī)范中關(guān)于字節(jié)碼文件結(jié)構(gòu)的知識(shí)的話,上面這段代碼肯定是看得一頭霧水,因?yàn)楸疚闹饕侵v解動(dòng)態(tài)代理,加上個(gè)人對Java虛擬機(jī)的掌握也是菜鳥級別,所以下面就簡單闡述一下關(guān)于字節(jié)碼結(jié)構(gòu)的內(nèi)容以便大家理解上面這塊代碼,但是不展開詳說。
Class文件結(jié)構(gòu)簡述
在Java虛擬機(jī)規(guī)范中,Class文件是一組二進(jìn)制流,每個(gè)Class文件會(huì)對應(yīng)一個(gè)類或者接口的定義信息,當(dāng)然,Class文件并不是一定以文件形式存在于硬盤,也有可能直接由類加載器加載到內(nèi)存。每一個(gè)Class文件加載到內(nèi)存后,經(jīng)過一系列的加載、連接、初始化過程,然后會(huì)在方法區(qū)中形成一個(gè)Class對象,作為外部訪問該類信息的的唯一入口。按照J(rèn)ava虛擬機(jī)規(guī)范,Class文件是具有非常嚴(yán)格嚴(yán)謹(jǐn)?shù)慕Y(jié)構(gòu)規(guī)范,由一系列數(shù)據(jù)項(xiàng)組成,各個(gè)數(shù)組項(xiàng)之間沒有分隔符的結(jié)構(gòu)緊湊排列。每個(gè)數(shù)據(jù)項(xiàng)會(huì)有相應(yīng)的數(shù)據(jù)類型,如下表就是一個(gè)完整Class文件結(jié)構(gòu)的表。

其中名稱一列就是組成Class文件的數(shù)據(jù)項(xiàng),限于篇幅這邊就不展開詳細(xì)解釋每一項(xiàng)了,大家有興趣可以自己去查點(diǎn)資料了解一下,左邊是其類型,主要分兩類,像u2,u4這類是無符號數(shù),分別表示2個(gè)字節(jié)和4個(gè)字節(jié)。以info結(jié)尾的是表結(jié)構(gòu),表結(jié)構(gòu)又是一個(gè)復(fù)合類型,由其它的無符號數(shù)和其他的表結(jié)構(gòu)組成。
我這邊以相對結(jié)構(gòu)簡單的field_info結(jié)構(gòu)舉個(gè)例子,field_info結(jié)構(gòu)用來描述接口或者類中的變量。它的結(jié)構(gòu)如下:

其它的表結(jié)構(gòu)method_info,attribute_info也都是類似,都會(huì)有自己特有的一套結(jié)構(gòu)規(guī)范。
好了,簡單了解一下Class文件結(jié)構(gòu)后,現(xiàn)在再回到我們的主題來,我們再來研究ProxyGenerator.generateClassFile()方法內(nèi)容就好理解了。其實(shí)這個(gè)方法就做了一件事情,就是根據(jù)我們傳入的這些個(gè)信息,再按照J(rèn)ava虛擬機(jī)規(guī)范的字節(jié)碼結(jié)構(gòu),用IO流的方式寫入到一個(gè)字節(jié)數(shù)組中,這個(gè)字節(jié)數(shù)組就是代理類的Class文件。默認(rèn)情況這個(gè)Class文件直接存在內(nèi)存中,為了更加深入理解動(dòng)態(tài)代理原理,該是時(shí)候去看看這個(gè)文件到底是啥結(jié)構(gòu)了。怎么看?還記得前面提到過的sun.misc.ProxyGenerator.saveGeneratedFiles嗎?只要我們往系統(tǒng)屬性中加入該參數(shù)并將其值設(shè)為true,就會(huì)自動(dòng)將該方法生成的byte[]形式的Class文件保存到硬盤上,如下代碼:
public class Client2 {
public static void main(String[] args) {
//加入該屬性并設(shè)置為true
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Person landlord = new Landlord();
Person proxy = (Person) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Person.class}, new RentHandler(landlord));
proxy.rent();
}
}
再次運(yùn)行,神奇的一幕發(fā)生了,工程中多了一個(gè)類,沒錯(cuò),這就是JDK動(dòng)態(tài)代理生成的代理類,因?yàn)槲覀兊慕涌谑莗ublic修飾,所以采用默認(rèn)包名com.sun.proxy,類名以$Proxy開頭,后面跟一個(gè)數(shù)字,和預(yù)期完全吻合。完美!🤩

那么就讓我們反編譯一下這個(gè)class文件看看它的內(nèi)容來一探究竟......
下面是反編譯得到的代理類的內(nèi)容,
public final class $Proxy0 extends Proxy implements Person { ★
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws { ②
super(var1);
}
public final boolean equals(Object var1) throws { ④
return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
}
public final void rent() throws { ③
super.h.invoke(this, m3, (Object[]) null);
}
public final String toString() throws { ④
return (String) super.h.invoke(this, m2, (Object[]) null);
}
public final int hashCode() throws { ④
return (Integer) super.h.invoke(this, m0, (Object[]) null);
}
static { ①
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.dujc.mybatis.proxy.Person").getMethod("rent");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
}
}
👉有幾個(gè)關(guān)注點(diǎn)
- 標(biāo)注①的是一個(gè)靜態(tài)代碼塊,當(dāng)代理類一被加載,會(huì)立刻初始化,用反射方式獲取得到被代理的接口中方法和Object中equals(),toString(),hashCode()方法的Method對象,并將其保存在屬性中,為后續(xù)請求分派做準(zhǔn)備。
- 標(biāo)注②的是帶有一個(gè)帶有InvocationHandler類型參數(shù)的構(gòu)造器,這個(gè)也驗(yàn)證了我們之前的猜測,沒錯(cuò),代理類會(huì)通過構(gòu)造器接收一個(gè)InvocationHandler實(shí)例,再觀察標(biāo)記★的地方,代理類繼承了Proxy類,其實(shí)代理類會(huì)通過調(diào)用父類構(gòu)造器將其保存在Proxy的屬性h中,自然會(huì)繼承給當(dāng)前這個(gè)代理類,這個(gè)InvocationHandler實(shí)例為后續(xù)請求分派做準(zhǔn)備。同時(shí)由此我們也可以得出結(jié)論,Proxy是所有的代理類的父類。另外再延伸,因?yàn)镴ava是一門單繼承語言,所以意味著代理類不可能再通過繼承其他類的方式來擴(kuò)展。所以,JDK動(dòng)態(tài)代理沒法對不實(shí)現(xiàn)任何接口的類進(jìn)行代理,原因就在于此。這或許也是動(dòng)態(tài)代理模式不多的缺點(diǎn)之一。如果需要繼承形式的類代理,可以使用CGLIB等類庫。
- 標(biāo)注③的是我們指定接口Person中的方法,標(biāo)注④的是代理類繼承自O(shè)bject類中的equals(),toString(),hashCode()方法。再觀察這些方法內(nèi)部實(shí)現(xiàn),所有的方法請求全部委托給之前由構(gòu)造器傳入的InvocationHandler實(shí)例的invoke()方法處理,將當(dāng)前的代理類實(shí)例,各方法的Method對象和方法參數(shù)傳入,最后返回執(zhí)行結(jié)果。由此得出結(jié)論,動(dòng)態(tài)代理過程中,所指定接口的方法以及Object中equals(),toString(),hashCode()方法會(huì)被代理,而Object其他方法則并不會(huì)被代理,而且所有的方法請求全部都是委托給我們自己寫的自定義InvocationHandler的invoke()方法統(tǒng)一處理,哇塞,O了,這樣的處理實(shí)在太優(yōu)雅了!
動(dòng)態(tài)代理到底有什么卵用
其實(shí)經(jīng)過上面這一堆講解,動(dòng)態(tài)代理模式中最核心的內(nèi)容基本都分析完了,相信大家應(yīng)該對其也有了一個(gè)本質(zhì)的認(rèn)知。學(xué)以致用,技術(shù)再牛逼如果沒法用在實(shí)際工作中也說實(shí)話也只能拿來裝逼了。那這個(gè)東西到底有什么卵用呢?其實(shí)我以前學(xué)完動(dòng)態(tài)代理模式后第一感覺是,嗯,這玩意確實(shí)挺牛逼的,但是到底有什么用?沒有一點(diǎn)概念。在閱讀Spring或者M(jìn)ybatis等經(jīng)典開源框架中的代碼時(shí),時(shí)不時(shí)也經(jīng)常會(huì)發(fā)現(xiàn)動(dòng)態(tài)代理模式的身影,但是還是沒有一個(gè)直接的感受。直到最近一段時(shí)間我在深入研究Mybatis源碼時(shí),看到其日志模塊的設(shè)計(jì),內(nèi)部就是使用了動(dòng)態(tài)代理,忽然靈光一閃,大受啟發(fā)感覺一下子全想通了......這就是冥冥之中注定的吧?😂所以最后我就拿這個(gè)例子給大家講解一下動(dòng)態(tài)代理模式的實(shí)際應(yīng)用場景。
想必使用過Mybatis這一優(yōu)秀持久層框架的人都注意到過,每當(dāng)我們執(zhí)行對數(shù)據(jù)庫操作,如果日志級別是DEBUG,控制臺(tái)會(huì)打印出一些輔助信息,比如執(zhí)行的SQL語句,綁定的參數(shù)和參數(shù)值,返回的結(jié)果等,你們有沒有想過這些信息到底是怎么來的?
在Mybatis底層的日志模塊中,有一塊專門用于打印JDBC相關(guān)信息日志的功能。這塊功能是由一系列xxxLogger類構(gòu)成。其中最頂層的是BaseJdbcLogger,他有4個(gè)子類,繼承關(guān)系如下圖:

看名字應(yīng)該就能猜出來是干啥了,以ConnectionLogger為例,下面是ConnectionLogger的關(guān)鍵代碼:
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler { ❶
private final Connection connection;
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn; ❷
}
@Override
public Object invoke(Object proxy, Method method, Object[] params) ❸
throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
}
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
}
怎么樣?是不是有種熟悉的感覺?🙀
👉觀察上面代碼,可以得出以下幾點(diǎn)結(jié)論:
- ConnectionLogger實(shí)現(xiàn)了InvocationHandler,通過構(gòu)造器傳入真實(shí)Connection對象,這是一個(gè)真實(shí)對象,并將其保存在屬性,后續(xù)請求會(huì)委托給它執(zhí)行。其靜態(tài)方法newInstance()內(nèi)部就是通過Proxy.newProxyInstance()并傳入類加載器等一系列參數(shù)返回一個(gè)Connection的代理對象給前端。該方法最終會(huì)在DEBUG日志級別下被org.apache.ibatis.executor.BaseExecutor.getConnection()方法調(diào)用返回一個(gè)Connection代理對象。
- 前面說過,JDK動(dòng)態(tài)代理會(huì)將客戶端所有的請求全部派發(fā)給InvocationHandler的invoke()方法,即上面ConnectionLogger中的invoke()方法。invoke()方法當(dāng)中,不難發(fā)現(xiàn),Mybatis對于Object中定義的方法,統(tǒng)一不做代理處理,直接調(diào)用返回。對于prepareStatement(),prepareCall(),createStatement()這三個(gè)核心方法會(huì)統(tǒng)一委托給真實(shí)的Connection對象處理,并且在執(zhí)行之前會(huì)以DEBUG方式打印日志信息。除了這三個(gè)方法,Connection其它方法也會(huì)被真實(shí)的Connection對象代理,但是并不會(huì)打印日志信息。我們以prepareStatement()方法為例,當(dāng)真實(shí)的Connection對象調(diào)用prepareStatement()方法會(huì)返回PreparedStatement對象,這又是一個(gè)真實(shí)對象,但是Mybatis并不會(huì)將該真實(shí)對象直接返回,而且通過調(diào)用PreparedStatementLogger.newInstance()再次包裝代理,看到這個(gè)方法名字,我相信聰明的您都能猜到這個(gè)方法的邏輯了。沒錯(cuò),PreparedStatementLogger類的套路和ConnectionLogger如出一轍。這邊我再貼回PreparedStatementLogger的代碼,
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
private final PreparedStatement statement;
private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.statement = stmt;
}
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
} else if (SET_METHODS.contains(method.getName())) {
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else if ("getUpdateCount".equals(method.getName())) {
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);
}
}
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
}
這個(gè)代碼的邏輯我就不講了,思路幾乎和ConnectionLogger完全一致。無非是攔截的方法不同,因?yàn)檫@次被代理對象是PreparedStatement,所以這次會(huì)去攔截都是PreparedStatement的方法,比如setXXX()系列,executeXX()系列等方法。然后在指定方法執(zhí)行前后添加需要的DEBUG日志信息,perfect!以getResultSet方法為例,PreparedStatement對象調(diào)用getResultSet()后,會(huì)返回真實(shí)的ResultSet對象,但是一樣的套路,并不會(huì)直接將該真實(shí)對象返回,而是由調(diào)用ResultSetLogger.newInstance()再次將該ResultSet對象包裝,ResultSetLogger的代碼相信聰明的您不需要我再花篇幅講了。
結(jié)束
好了,關(guān)于JDK動(dòng)態(tài)代理的核心原理部分到這里算全部講解完畢了,其實(shí)我們聊了這么多,都是圍繞著java.lang.reflect.Proxy.newProxyInstance()這個(gè)方法展開的。其實(shí)在Proxy類中,還有一個(gè)getProxyClass()方法,這個(gè)只需要傳入加載代理類的類加載器和指定接口就可以動(dòng)態(tài)生成其代理類,我一開始說到靜態(tài)代理弊病的時(shí)候說過,靜態(tài)代理創(chuàng)建代理時(shí),真實(shí)角色必須要存在,否則這個(gè)模式?jīng)]法進(jìn)行下去,但是JDK動(dòng)態(tài)代理可以做到在真實(shí)角色不存在的情況下就返回該接口的代理類。至于Proxy其它的方法都比較簡單了,此處不再贅述。
今天和大家一起探索JDK動(dòng)態(tài)代理模式原理的技術(shù)之旅到此結(jié)束,希望這篇文章可以給大家?guī)韺W(xué)習(xí)或者工作上的幫助,也不枉我一個(gè)字一個(gè)字的手敲了這么多字......🥺以后相信大家對莫測高深的動(dòng)態(tài)代理模式也不會(huì)再談“動(dòng)態(tài)代理”色變了。接下去,我會(huì)繼續(xù)抽出空閑時(shí)間給大家分享自己學(xué)習(xí)工作過程踩過的坑,思考過的成果,分享他人同時(shí)也對自己的知識(shí)掌握輸出整理,也希望大家可以繼續(xù)關(guān)注我,咱們下次不見不散。😋
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對腳本之家的支持。
- Java簡單實(shí)現(xiàn)動(dòng)態(tài)代理模式過程解析
- Java代理模式實(shí)例詳解【靜態(tài)代理與動(dòng)態(tài)代理】
- Java設(shè)計(jì)模式之動(dòng)態(tài)代理模式實(shí)例分析
- JAVA動(dòng)態(tài)代理模式(從現(xiàn)實(shí)生活角度理解代碼原理)
- java 代理模式及動(dòng)態(tài)代理機(jī)制深入分析
- 詳解java動(dòng)態(tài)代理模式
- java代理模式與動(dòng)態(tài)代理模式詳解
- 代理模式之Java動(dòng)態(tài)代理實(shí)現(xiàn)方法
- Java代理模式與動(dòng)態(tài)代理之間的關(guān)系以及概念
相關(guān)文章
SpringBoot整合RestTemplate用法的實(shí)現(xiàn)
本篇主要介紹了RestTemplate中的GET,POST,PUT,DELETE、文件上傳和文件下載6大常用的功能,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
Java調(diào)用第三方http接口的四種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java調(diào)用第三方http接口的四種方式,在實(shí)際開發(fā)中我們經(jīng)常會(huì)與第三方公司進(jìn)行合作,接入第三方接口,文中給出了詳細(xì)的代碼實(shí)例,需要的朋友可以參考下2023-08-08
Java遞歸算法經(jīng)典實(shí)例(經(jīng)典兔子問題)
本文主要對經(jīng)典的兔子案例分析,來進(jìn)一步更好的理解和學(xué)習(xí)java遞歸算法,具有很好的參考價(jià)值,需要的朋友一起來看下吧2016-12-12
Java關(guān)鍵字詳解之final static this super的用法
this用來調(diào)用目前類自身的成員變量,super多用來調(diào)用父類的成員,final多用來定義常量用的,static定義靜態(tài)變量方法用的,靜態(tài)變量方法只能被類本身調(diào)用,下文將詳細(xì)介紹,需要的朋友可以參考下2021-10-10
springboot yml配置文件使用@project.xxxx@啟動(dòng)報(bào)錯(cuò)Do not
這篇文章主要介紹了springboot yml配置文件使用@project.xxxx@啟動(dòng)報(bào)錯(cuò)Do not use @ for indentation問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot集成elasticsearch使用圖文詳解
Spring Boot集成Elasticsearch其實(shí)非常簡單,這篇文章主要給大家介紹了關(guān)于SpringBoot集成elasticsearch使用的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04

