談?wù)?Java 反射機制,動態(tài)代理是基于什么原理?
典型回答
反射機制是 Java 語言提供的一種基礎(chǔ)功能,賦予程序在運行時自?。╥ntrospect)的能力,通過反射我們可以直接操作類或者對象,比如獲取某個對象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對象,甚至可以運行時修改類定義。通過運行時操作元數(shù)據(jù)或?qū)ο螅琂ava 可以靈活地操作運行時才能確定的信息。
動態(tài)代理是一種方便運行時動態(tài)構(gòu)建代理、動態(tài)處理代理方法調(diào)用的機制。
實現(xiàn)動態(tài)代理的方式很多,比如 JDK 自身提供的動態(tài)代理,就是主要利用了上面提到的反射機制。還有利用更高性能的字節(jié)碼操作機制,類似 ASM、cglib(基于ASM)、Javassist 等。很多繁瑣的重復(fù)編程,都可以被動態(tài)代理機制優(yōu)雅地解決。
反射機制及其演進(jìn)
查看java.lang或java.lang.reflect包下的相關(guān)抽象,Class、Field、Method、Constructor等,這些完全就是我們?nèi)ゲ僮黝惡蛯ο蟮脑獢?shù)據(jù)對應(yīng)。
AccessibleObject.setAccessible(boolean)可以運行時修改成員的訪問權(quán)限。
但是,在 Java 9 以后,這個方法的使用可能會存在一些爭議。因為 Jigsaw 項目新增的模塊化系統(tǒng),出于強封裝性的考慮,對反射訪問進(jìn)行了限制。Jigsaw 引入了所謂 Open 的概念,只有當(dāng)被反射操作的模塊和指定的包對反射調(diào)用者模塊Open,才能使用 setAccessible;否則,被認(rèn)為不合法操作。如果我們的實體類是定義在模塊里面,我們需要在模塊描述符中明確聲明:
module MyEntities {
// Open for reflection
opens com.mycorp to java.persistence;
}
動態(tài)代理
首先,它是一個代理機制。很多動態(tài)代理場景,可以看作是裝飾器(Decorator)模式的應(yīng)用。通過代理可以讓調(diào)用者與實現(xiàn)者之間解耦。
代理的發(fā)展經(jīng)歷了靜態(tài)到動態(tài)的過程,源于靜態(tài)代理引入的額外工作。
JDK 動態(tài)代理的一個簡單例子。下面只是加了一句 print,在生產(chǎn)系統(tǒng)中,我們可以輕松擴展類似邏輯進(jìn)行診斷、限流等。
public class MyDynamicProxy {
public static void main (String[] args) {
HelloImpl hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 構(gòu)造代碼實例
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
// 調(diào)用代理方法
proxyHello.sayHello();
}
}
interface Hello {
void sayHello();
}
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target, args);
return result;
}
}
從 API 設(shè)計和實現(xiàn)的角度,這種實現(xiàn)仍然有局限性,因為它是以接口為中心的,相當(dāng)于添加了一種對于被調(diào)用者沒有太大意義的限制。我們實例化的是 Proxy 對象,而不是真正的被調(diào)用類型,這在實踐中還是可能帶來各種不便和能力退化。
如果被調(diào)用者沒有實現(xiàn)接口,可以使用cglib方式。
Spring AOP 支持兩種模式的動態(tài)代理,JDK Proxy或者cglib。
cglib 動態(tài)代理采取的是創(chuàng)建目標(biāo)類的子類的方式,因為是子類化,我們可以達(dá)到近似使用被調(diào)用者本身的效果。
JDK Proxy 的優(yōu)勢:
- 最小化依賴關(guān)系,減少依賴意味著簡化開發(fā)和維護(hù),JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑進(jìn)行 JDK 版本升級,而字節(jié)碼類庫通常需要進(jìn)行更新以保證在新版jdk中能使用。
- 代碼實現(xiàn)簡單。
基于類似 cglib 框架的優(yōu)勢:
- 有的時候調(diào)用目標(biāo)可能不便實現(xiàn)額外接口,從某種角度看,限定調(diào)用者實現(xiàn)接口是有些侵入性的實踐。
- 只操作我們關(guān)心的類,而不必為其他相關(guān)類增加工作量。
- 高性能
動態(tài)代理應(yīng)用非常廣泛,它完美符合 Spring AOP 等切面編程??梢钥醋魇菍?OOP 的一個補充,因為 OOP 對于跨越不同對象或類的分散、糾纏邏輯表現(xiàn)力不夠,比如在不同模塊的特定階段做一些事情,類似日志、用戶鑒權(quán)、全局性異常處理、性能監(jiān)控,甚至事務(wù)處理等。
AOP 通過(動態(tài))代理機制可以讓開發(fā)者從這些繁瑣事項中抽身出來,大幅度提高了代碼的抽象程度和復(fù)用度。