文章目錄
參考文章
前言
代理可以看做是一種封裝, 最終會把功能調用傳遞給背後真正的實現方。 使用代理的原因往往是因爲背後的真實實現方不便於直接被調用(可能是一個遠程對象) 或者需要爲被代理的功能添加一些輔助功能。
注: 根據 GoF 設計模式原書的描述, 代理模式和裝飾器模式的實現方式是完全一樣的, 其區別主要在於使用場景
- 代理模式的目的在於進行 訪問控制, 其意圖在於爲被代理對象提供一個替身, 而之所以提供替身而不直接訪問的目的是, 原有的被代理對象可能不便於被直接訪問(是一個遠程對象), 或者是一個訪問權限需要被限制的對象。
- 裝飾器模式的目的在於進行功能添加 / 功能增強 , 其意圖在於爲被裝飾對象動態地增加功能或屬性
筆者認爲這種說法是比較合理的, 畢竟一個代理,從業務邏輯角度來講, 對被代理方應該是透明的。 就好比你委託一箇中介替你完成一些事情, 他所達成的結果,應當和你的期待一致, 不應該有差別。所以代理如果要爲被代理添加一些功能的話, 也應該是業務邏輯無關的功能,例如日誌統計等功能。
動態代理和靜態代理的區別:
- 動態代理 中的代理關係是程序運行時產生的, 支持動態變化。 靜態代理 的對象間代理關係是編譯前就已經確定的, 在運行時刻, 是固定不變的。
- 動態代理可以通過單一的一個類中的一個方法, 爲多個類的多個方法提供代理服務。 動態代理允許你編寫代碼爲任意的一種接口提供代理服務。
- 其背後的工作原理,是針對特定接口, 創建代理類,使用者可以在這個代理類 Proxy 身上直接調用各種接口方法, 而代理類會把所有的方法調用都轉發給一個單一的
handler
的invoke
方法, 完成真正的邏輯。
雖然動態代理在日常開發中並不經常使用,但是對於框架編寫者而言, 動態代理非常實用。
動態代理也是 Spring AOP 的實現基礎, 本文旨在梳理一下 Java 內置的動態代理實現方式, 供初學者學習參考
JAVA 原生動態代理
java 自帶的動態代理方式是構建在 JDK 中的一個特性, 所以不需要引入任何額外的依賴即可使用。
但是有一個限制: java 內置的動態代理必須依託於接口
jdk 動態代理主要涉及到以下3個概念
- 接口(Interface )
- 這裏以一個
FooInterface
進行舉例,FooInterface
中有一個foo()
方法必須實現
- 這裏以一個
- 代理類實例 ( Proxy Instance)
proxyInstance
是我們通過動態代理創建的代理類實例, 對外表現爲實現了FooInterface
接口
- 調用處理器(Invocation Handler)
- 對於
proxyInstance
的方法調用, 會實際觸發invocationHandler.invoke()
函數
- 對於
上述三個角色的邏輯工作關係是這樣的
- Tips:
invocationHandler.invoke()
方法, 常常會把針對 proxyInstance 的調用進一步轉發給其他類,這其實是引入了多層代理關係, 即 interface --> invocationHandler–> interfaceImpl 這有時會讓部分讀者產生困惑, 搞不清動態代理的核心工作機制。 - 實際上, 讀者僅需要記住, 動態代理本身最基礎的邏輯就是完成一層代理關係, 即將針對某一接口的調用, 全部轉發給一個 InvocationHandler 接口實現類的 invoke 方法。
- 下面的例子一和例子二, 分別展示了單層代理關係與多層代理關係的實現方式。
例子一:一個只會打印被調用方法的代理
下面通過一個例子進一步展現使用流程。 我們的目標是創建一個 Map 接口的代理類實例 fakedHashMap, 調用代代理類所實現的 Map
接口中的 get(),
put()
方法時, 什麼都不執行, 僅僅打印被執行的方法名稱
- 接口(Interface ) -->
Map
- 被代理類(Target Class) -->
無
- 代理類實例 ( Proxy Instance) -->
fakedMap
- 調用處理器(Invocation Handler) -->
MapInvocationHandler
步驟一: 創建 Invocation Handler
InvocationHandler
的 invoke
public class DynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
DynamicInvocationHandler.class);
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
LOGGER.info("Invoked method: {}", method.getName());
return 42;
}
}
步驟二: 創建 Proxy Instance
Map proxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { Map.class },
new DynamicInvocationHandler());
創建好代理類實例 proxyInstance 後, 即可調用被代理的 Map 的接口方法
proxyInstance.put("hello", "world"); // 調用後控制檯輸出 Invoked method: put
例子二:可以代理多個接口的 InvocationHandler
在這個例子中,我們的目標是創建多個類的代理實例, 調用代理實例 proxyInstance 的對應方法後, 除了可以調用到真正的實現類方法, 還可以統計到實現類方法實際執行的耗時。
步驟一: 創建 TimingInvocationHandler
public class TimingDynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
TimingDynamicInvocationHandler.class);
private final Map<String, Method> methods = new HashMap<>();
private Object target;
// 把接口實現類中所實現的 methood 全部記錄到 map 中, 後續方便轉發
public TimingDynamicInvocationHandler(Object target) {
this.target = target;
for(Method method: target.getClass().getDeclaredMethods()) {
this.methods.put(method.getName(), method);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long start = System.nanoTime();
Object result = methods.get(method.getName()).invoke(target, args);
long elapsed = System.nanoTime() - start;
LOGGER.info("Executing {} finished in {} ns", method.getName(),
elapsed);
return result;
}
}
步驟二: 創建代理實例並調用
Map mapProxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class },
new TimingDynamicInvocationHandler(new HashMap<>()));
mapProxyInstance.put("hello", "world");
CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { CharSequence.class },
new TimingDynamicInvocationHandler("Hello World"));
csProxyInstance.length()
注意到, 我們用同一個 InvocationHandler 創建了多個接口(Map, CharSequence)的代理實例, 並將這些接口的調用最終分別轉發到了 HashMap 以及 CharSequence 的實例上。
總結
java 內置實現的動態代理本質是通過一種手段, 創建一個虛擬實現了某 Interface
的代理實例 proxyInstance
, 對於該 proxyInstance
直接可以調用 Interface
中的任意方法, 最終會觸發對應的 InvocationHandler
中的 invoke()
函數。 至於 InvocationHandler
最終是否進一步調用真正的 Interface
實現類, 完全取決於自定義的 InvocationHandler
邏輯。
整體工作流程可以簡化爲下圖