關(guān)于Java的動(dòng)態(tài)代理機(jī)制
靜態(tài)代理
常規(guī)的代理模式有以下三個(gè)部分組成:
功能接口
interface IFunction {
void doAThing();
}功能提供者
class FunctionProvider implement IFunction {
public void doAThing {
System.out.print("do A");
}
}功能代理者
class Proxy implement IFunction {
private FunctionProvider provider;
Proxy(FunctionProvider provider) {
this.provider = provider;
}
public void doAThing {
provider.doAThing();
}
}前兩者就是普通的接口和實(shí)現(xiàn)類,而第三個(gè)就是所謂的代理類。對(duì)于使用者而言,他會(huì)讓代理類去完成某件任務(wù),并不關(guān)心這件任務(wù)具體的跑腿者。
這就是靜態(tài)代理,好處是方便調(diào)整變換具體實(shí)現(xiàn)類,而使用者不會(huì)受到任何影響。
不過這種方式也存在弊端:比如有多個(gè)接口需要進(jìn)行代理,那么就要為每一個(gè)功能提供者創(chuàng)建對(duì)應(yīng)的一個(gè)代理類,那就會(huì)越來越龐大。而且,所謂的“靜態(tài)”代理,意味著必須提前知道被代理的委托類。
通過下面一個(gè)例子來說明下:
統(tǒng)計(jì)函數(shù)耗時(shí)–靜態(tài)代理實(shí)現(xiàn)
現(xiàn)在希望通過一個(gè)代理類,對(duì)我感興趣的方法進(jìn)行耗時(shí)統(tǒng)計(jì),利用靜態(tài)代理有如下實(shí)現(xiàn):
interface IAFunc {
void doA();
}
interface IBFunc {
void doB();
}
class TimeConsumeProxy implement IAFunc, IBFunc {
private AFunc a;
private BFunc b;
public(AFunc a, BFunc b) {
this.a = a;
this.b = b;
}
void doA() {
long start = System.currentMillions();
a.doA();
System.out.println("耗時(shí):" + (System.currentMillions() - start));
}
void doB() {
long start = System.currentMillions();
b.doB();
System.out.println("耗時(shí):" + (System.currentMillions() - start));
}
}弊端很明顯,如果接口越多,每新增一個(gè)函數(shù)都要去修改這個(gè) TimeConsumeProxy 代理類:把委托類對(duì)象傳進(jìn)去,實(shí)現(xiàn)接口,在函數(shù)執(zhí)行前后統(tǒng)計(jì)耗時(shí)。
這種方式顯然不是可持續(xù)性的,下面來看下使用動(dòng)態(tài)代理的實(shí)現(xiàn)方式,進(jìn)行對(duì)比。 ###動(dòng)態(tài)代理
動(dòng)態(tài)代理的核心思想是通過 Java Proxy 類,為傳入進(jìn)來的任意對(duì)象動(dòng)態(tài)生成一個(gè)代理對(duì)象,這個(gè)代理對(duì)象默認(rèn)實(shí)現(xiàn)了原始對(duì)象的所有接口。
還是通過統(tǒng)計(jì)函數(shù)耗時(shí)例子來說明更加直接。
統(tǒng)計(jì)函數(shù)耗時(shí)–動(dòng)態(tài)代理實(shí)現(xiàn)
interface IAFunc {
void doA();
}
interface IBFunc {
void doB();
}
class A implement IAFunc { ... }
class B implement IBFunc { ... }
class TimeConsumeProxy implements InvocationHandler {
private Object realObject;
public Object bind(Object realObject) {
this.realObject = realObject;
Object proxyObject = Proxy.newInstance(
realObject.getClass().getClassLoader(),
realObject.getClass().getInterfaces(),
this
);
return proxyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentMillions();
Object result = method.invoke(target, args);
System.out.println("耗時(shí):" + (System.currentMillions() - start));
return result;
}
}具體使用時(shí):
public static void main(String[] args) {
A a = new A();
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);
aProxy.doA();
B b = new B();
IBFunc bProxy = (IBFunc) new TimeConsumeProxy().bind(b);
bProxy.doB();
}這里最大的區(qū)別就是:代理類和委托類 互相透明獨(dú)立,邏輯沒有任何耦合,在運(yùn)行時(shí)才綁定在一起。這也就是靜態(tài)代理與動(dòng)態(tài)代理最大的不同,帶來的好處就是:無論委托類有多少個(gè),代理類不受到任何影響,而且在編譯時(shí)無需知道具體委托類。
回到動(dòng)態(tài)代理本身,上面代碼中最重要的就是:
Object proxyObject = Proxy.newInstance( realObject.getClass().getClassLoader(), realObject.getClass().getInterfaces(), this );
通過 Proxy 工具,把真實(shí)委托類轉(zhuǎn)換成了一個(gè)代理類,最開始提到了一個(gè)代理模式的三要素:功能接口、功能提供者、功能代理者;在這里對(duì)應(yīng)的就是:realObject.getClass().getInterfaces(),realObject,TimeConsumeProxy。
其實(shí)動(dòng)態(tài)代理并不復(fù)雜,通過一個(gè) Proxy 工具,為委托類的接口自動(dòng)生成一個(gè)代理對(duì)象,后續(xù)的函數(shù)調(diào)用都通過這個(gè)代理對(duì)象進(jìn)行發(fā)起,最終會(huì)執(zhí)行到 InvocationHandler#invoke 方法,在這個(gè)方法里除了調(diào)用真實(shí)委托類對(duì)應(yīng)的方法,還可以做一些其他自定義的邏輯,比如上面的運(yùn)行耗時(shí)統(tǒng)計(jì)等。
探索動(dòng)態(tài)代理實(shí)現(xiàn)機(jī)制
好了,上面我們已經(jīng)把動(dòng)態(tài)代理的基本用法及為什么要用動(dòng)態(tài)代理進(jìn)行了講解,很多文章到這里也差不多了,不過我們還準(zhǔn)備進(jìn)一步探索一下給感興趣的讀者。
拋出幾個(gè)問題:
上面生成的代理對(duì)象 Object proxyObject 究竟是個(gè)什么東西?為什么它可以轉(zhuǎn)型成 IAFunc,還能調(diào)用doA() 方法? 這個(gè) proxyObject 是怎么生成出來的?它是一個(gè) class 嗎?
下面我先給出答案,再一步步探究這個(gè)答案是如何來的。 問題一: proxyObject 究竟是個(gè)什么 -> 動(dòng)態(tài)生成的 $Proxy0.class 文件
在調(diào)用 Proxy.newInstance 后,Java 最終會(huì)為委托類 A 生成一個(gè)真實(shí)的 class 文件:$Proxy0.class,而 proxyObject 就是這個(gè) class 的一個(gè)實(shí)例。
猜一下,這個(gè) $Proxy0.class 類長(zhǎng)什么樣呢,包含了什么方法呢?回看下剛剛的代碼:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a); aProxy.doA();
推理下,顯然這個(gè) $Proxy0.class 實(shí)現(xiàn)了 IAFunc 接口,同時(shí)它內(nèi)部也實(shí)現(xiàn)了 doA() 方法,而且重點(diǎn)是:這個(gè) doA() 方法在運(yùn)行時(shí)會(huì)執(zhí)行到 TimeConsumeProxy#invoke() 方法里。
重點(diǎn)來了!下面我們來看下這個(gè) $Proxy0.class 文件,把它放進(jìn) IDE 反編譯下,可以看到如下內(nèi)容,來驗(yàn)證下剛剛的猜想:
final class $Proxy0 extends Proxy implements IAFunc {
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 {
// 省略
}
public final void doA() throws {
try {
// 劃重點(diǎn)
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
// 省略
}
public final int hashCode() throws {
// 省略
}
static {
try {
// 劃重點(diǎn)
m3 = Class.forName("proxy.IAFunc").getMethod("doA", 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]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}沒錯(cuò),剛剛的猜想都中了!實(shí)現(xiàn)了 IAFunc 接口和 doA() 方法,不過,doA()里是什么鬼?
super.h.invoke(this, m3, (Object[])null);
回看下,TimeConsumeProxy里面的 invoke 方法,它的函數(shù)簽名是啥?
public Object invoke(Object proxy, Method method, Object[] args);
沒錯(cuò),doA()里做的就是調(diào)用 TimeConsumeProxy#invoke() 方法。
那么也就是說,下面這段代碼執(zhí)行流程如下:
IAFunc aProxy = (IAFunc) new TimeConsumeProxy().bind(a);aProxy.doA();
基于傳入的委托類 A,生成一個(gè)$Proxy0.class 文件; 創(chuàng)建一個(gè) $Proxy0.class 對(duì)象,轉(zhuǎn)型為 IAFunc 接口; 調(diào)用 aProxy.doA() 時(shí),自動(dòng)調(diào)用 TimeConsumeProxy 內(nèi)部的 invoke 方法。
問題二:proxyObject 是怎么一步步生成出來的 -> $Proxy0.class 文件生成流程
剛剛從末尾看了結(jié)果,現(xiàn)在我們回到代碼的起始端來看:
Object proxyObject = Proxy.newInstance( realObject.getClass().getClassLoader(), realObject.getClass().getInterfaces(), this );
準(zhǔn)備好,開始發(fā)車讀源碼了。我會(huì)截取重要的代碼并加上注釋。
先看Proxy.newInstance():
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 復(fù)制要代理的接口
final Class<?>[] intfs = interfaces.clone();
// 重點(diǎn):生成 $Proxy0.class 文件并通過 ClassLoader 加載進(jìn)來
Class<?> cl = getProxyClass0(loader, intfs);
// 對(duì) $Proxy0.class 生成一個(gè)實(shí)例,就是 `proxyObject`
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
}再來看 getProxyClass0 的具體實(shí)現(xiàn):ProxyClassFactory工廠類:
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
// 參數(shù)為 ClassLoader 和要代理的接口
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 1. 驗(yàn)證 ClassLoader 和接口有效性
for (Class<?> intf : interfaces) {
// 驗(yàn)證 classLoader 正確性
Class<?> interfaceClass = Class.forName(intf.getName(), false, loader);
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// 驗(yàn)證傳入的接口 class 有效
if (!interfaceClass.isInterface()) { ... }
// 驗(yàn)證接口是否重復(fù)
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { ... }
}
// 2. 創(chuàng)建包名及類名 $Proxy0.class
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 3. 創(chuàng)建 class 字節(jié)碼內(nèi)容
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
// 4. 基于字節(jié)碼和類名,生成 Class<?> 對(duì)象
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
}再看下第三步生成 class 內(nèi)容 ProxyGenerator.generateProxyClass:
// 添加 hashCode equals toString 方法
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 添加委托類的接口實(shí)現(xiàn)
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
// 添加構(gòu)造函數(shù)
methods.add(this.generateConstructor());這里構(gòu)造好了類的內(nèi)容:添加必要的函數(shù),實(shí)現(xiàn)接口,構(gòu)造函數(shù)等,下面就是要寫入上一步看到的 $Proxy0.class 了。
ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); dout.writeInt(0xCAFEBABE); ... dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); ... return bout.toByteArray();
到這里就生成了第一步看到的 $Proxy0.class 文件了,完成閉環(huán),講解完成! ###動(dòng)態(tài)代理小結(jié)
通過上面的講解可以看出,動(dòng)態(tài)代理可以隨時(shí)為任意的委托類進(jìn)行代理,并可以在 InvocationHandler#invoke 拿到運(yùn)行時(shí)的信息,并可以做一些切面處理。
在動(dòng)態(tài)代理背后,其實(shí)是為一個(gè)委托類動(dòng)態(tài)生成了一個(gè) $Proxy0.class 的代理類,該代理類會(huì)實(shí)現(xiàn)委托類的接口,并把接口調(diào)用轉(zhuǎn)發(fā)到 InvocationHandler#invoke 上,最終調(diào)用到真實(shí)委托類的對(duì)應(yīng)方法。
動(dòng)態(tài)代理機(jī)制把委托類和代理類進(jìn)行了隔離,提高了擴(kuò)展性。 ###Java 動(dòng)態(tài)代理與 Python 裝飾器
這是 Java 語(yǔ)言提供的一個(gè)有意思的語(yǔ)言特性,而其實(shí) Python 里也提供了一種類似的特性:裝飾器,可以達(dá)到類似的面相切面編程思想,下次有空再把兩者做下對(duì)比,這次先到這。
到此這篇關(guān)于關(guān)于Java的動(dòng)態(tài)代理機(jī)制的文章就介紹到這了,更多相關(guān)Java的動(dòng)態(tài)代理機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 數(shù)據(jù)結(jié)構(gòu) 冒泡排序?qū)崿F(xiàn)代碼
這篇文章主要介紹了java 數(shù)據(jù)結(jié)構(gòu) 冒泡排序的相關(guān)資料,并附實(shí)例代碼,有需要的小伙伴可以參考下2016-09-09
解決java文件流處理異常 mark/reset not supported問題
這篇文章主要介紹了解決java文件流處理異常 mark/reset not supported問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
springboot整合JPA訪問Mysql的實(shí)現(xiàn)方法
本文主要介紹了springboot整合JPA訪問Mysql的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
一篇文章帶你入門java算術(shù)運(yùn)算符(加減乘除余,字符連接)
這篇文章主要介紹了Java基本數(shù)據(jù)類型和運(yùn)算符,結(jié)合實(shí)例形式詳細(xì)分析了java基本數(shù)據(jù)類型、數(shù)據(jù)類型轉(zhuǎn)換、算術(shù)運(yùn)算符、邏輯運(yùn)算符等相關(guān)原理與操作技巧,需要的朋友可以參考下2021-08-08
Spring線程池ThreadPoolExecutor配置并且得到任務(wù)執(zhí)行的結(jié)果
今天小編就為大家分享一篇關(guān)于Spring線程池ThreadPoolExecutor配置并且得到任務(wù)執(zhí)行的結(jié)果,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
SpringBoot啟動(dòng)異常Exception in thread “main“ 
本文主要介紹了SpringBoot啟動(dòng)異常Exception in thread “main“ java.lang.UnsupportedClassVersionError,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
SpringMVC源碼解讀之 HandlerMapping - AbstractDetectingUrlHandlerM
這篇文章主要介紹了SpringMVC源碼解讀之 HandlerMapping - AbstractDetectingUrlHandlerMapping系列初始化的相關(guān)資料,需要的朋友可以參考下2016-02-02
Java語(yǔ)言實(shí)現(xiàn)反轉(zhuǎn)鏈表代碼示例
這篇文章主要介紹了Java語(yǔ)言實(shí)現(xiàn)反轉(zhuǎn)鏈表代碼示例,小編覺得挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-10-10

