靜態代理和動態代理
代理模式(靜態代理)
代理模式是為其他對象提供一種代理以控制對這個對象的訪問。
定義上也不算好理解, 上一個 《大話設計模式》 的圖。
Subject 類(一般是抽象類或接口), 定義了一個方法。
RealSubject 類實現了這個接口, 正常的情況我們只需要 new 出這個類的實例對象, 然後調用這個方法就可以了。
但是, 如果我們有需求說想要在這個方法的前面和後面進行一些操作, 那麽,原始的方法已經無法滿足了。
以租房子打比方:
房東李老板有一套房子, 他打算出租, 但是帶人看了幾次房子以後, 發現自己沒有那麽多時間。 於是他把房子委托給了中介公司小王, 小王幫他帶人看房子,然後收取中介費。
在該例子中, 房子是李老板的, 他(RealSubject)將租房子這個權限委托給了小王(Proxy)。
抽象
首先, 抽象出房東這個接口, 而李老板是它的具體實現。
public interface Landlord {
boolean rental();
}
具體對象
房東是一個抽象意義上的概念, 而李老板, 是一個具有意義上的房東, 我們稱之為是房東的一個實現。
public class LandlordImpl implements Landlord { public boolean rental() { System.out.println("李老板的房子出租啦"); return true; } }
代理對象
代理代表真實對象的功能以及在此基礎上添加訪問控制。
那就是中介小王, 他獲得了李老板的授權, 可以帶人去看房子, 但李老板明確了自己的條件, 房租至少5000, 如果是美女可以考慮降一點(小王就代表李老板進行一些過濾(訪問控制))。
如果租客滿意了, 打算租房子就跟小王說。 小王當然想租客租給租客, 有傭金嘛。但是, 具體的房子租不租還是要看房東。
我們就可以定義中介小王是房東的代理對象。他代表租房子以及訪問控制。
public class LandlordProxy implements Landlord{ private final Landlord landlord; public LandlordProxy(Landlord landlord) { this.landlord = landlord; } public boolean rental() { beforeRental(); if (landlord.rental()) { afterRental(); } return false; } private void afterRental() { System.out.println("收取傭金"); } private void beforeRental() { System.out.println("帶人看房子"); } }
如果有一天, 李老板覺得一天最多接受 5 次看房。
那顯然, 這是需要在小王那進行控制的。那麽我們的類就可以這麽改
public class LandlordProxy implements Landlord{
private final Landlord landlord;
private static final int NUM_ALLOWED = 5;
private int numPersons = 1;
public LandlordProxy(Landlord landlord) {
this.landlord = landlord;
}
public boolean rental() {
if (numPersons < NUM_ALLOWED) {
System.out.println("今天李老板不接客了");
} else {
beforeRental();
numPersons++;
if (landlord.rental()) {
afterRental();
}
}
return false;
}
private void afterRental() {
System.out.println("收取傭金");
}
private void beforeRental() {
System.out.println("帶人看房子");
}
}
代理對象的特征是
- 和被代理對象實現了同一個接口;
- 內部含有一個被代理對象的示例;
優點
- 真實對象可以專註於自己的業務邏輯控制;
- 非業務邏輯相關的部分, 可以通過代理類來處理;
- 隱藏了真實的對象, 對外只暴露代理對象。
- 擴展性:由於實現了相同的接口, 因此被代理對象的邏輯不管如何變化, 代理對象都不需要更改。
使用場合
- 控制對真實對象的訪問;
- 實現日誌記錄;
- 統計對象的訪問數量;
- 等等。
一些思考
在代理對象內部, 真實對象是什麽時候被初始化的, 以及初始化的對象是由誰產生的?
在我看過的代碼和書籍中, 有如下幾種, 對此我也是很困惑, 以下是我的一些見解
不推薦
- 將真實對象在代理對象構造函數內部初始化出來。
public LandlordProxy() {
landlord = new LandlordImpl();
}
- 構造函數不做改變, 在使用方法時
public boolean rental() {
if(landlord==null){
landlord = new LandlordImpl();
}
if (numPersons < NUM_ALLOWED) {
System.out.println("今天李老板不接客了");
} else {
beforeRental();
numPersons++;
if (landlord.rental()) {
afterRental();
}
}
return false;
}
以上兩種的思想都是在代理對象內部對真實對象進行初始化, 我個人不是很贊同這種做法。
如果我們代理對象代理的不僅僅是李老板, 又代理了王老板, 那麽怎麽辦?要寫兩個基本一模一樣的代理類?
推薦做法
就是我們使用的, 在構造函數中傳入
public LandlordProxy(Landlord landlord) {
this.landlord = landlord;
}
將真實對象的初始化交給調用者來進行。
這樣, 不管是什麽老板, 他們可以自己管理自己的租房方法, 但是相同的工作由代理來做, 而只需要一個代理類。實現了代碼的復用。
動態代理
如果是一兩個方法需要進行代理, 我們使用靜態代理那挺好。
但如果我們的接口中有 20 個方法, 每個方法都需要在前後加上前後的邏輯, 比如說記錄一下日誌。那麽, 我們就一直需要做一些重復性的工作, 相同的代碼需要寫很多遍, 不單單是寫的時候痛苦, 後面維護起來也很難受。
基於此, 動態代理誕生了。
在程序運行時運用反射機制動態創建代理類
在 Java 中, 實現動態代理很簡單
定義一個實現InvocationHandler
的類
如下所示
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(System.currentTimeMillis() + " 進入了方法");
// 中間是這個方法
Object result = method.invoke(target, args);
System.out.println(System.currentTimeMillis() + " 方法執行完畢");
return result;
}
}
target
是我們需要代理的真實對象, 該參數一樣建議在構造函數中傳入。
實現 invoke
方法,方法內部在前後實現我們需要的邏輯, 中間就是
Object result = method.invoke(target, args);
最後返回 result
即可。
調用定義的類
public static void main(String[] args) {
Landlord landlord = new LandloadImpl();
DynamicProxy dynamicProxy = new DynamicProxy(landlord);
// 這一步是關鍵
Landlord landlordProxy = (Landlord) Proxy.newProxyInstance(
landlord.getClass().getClassLoader(),
landlord.getClass().getInterfaces(),
dynamicProxy
);
landlordProxy.rental();
}
其實我們就是要使用 JDK 給我們提供的 newProxyInstance
函數, 該函數返回對應代理的對象的接口, 我們調用相應的方法即可。
優化
newProxyInstance
方法需要傳入的參數可以進一步進行優化。
我們在 DynamicProxy
方法中, 可以加入該函數。
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
這樣在我們獲得代理對象時候就簡單很多,main
函數可以改寫成這樣
public static void main(String[] args) {
Landlord landlord = new LandloadImpl();
DynamicProxy dynamicProxy = new DynamicProxy(landlord);
Landlord landlordProxy = dynamicProxy.getProxy();
landlordProxy.rental();
}
動態代理為我們帶來了方便, 但是呢, JDK 所提供的動態代理, 我們通過 newProxyInstance 函數可以知道, 被代理的對象必須要實現某個接口。
如果我們要代理的對象沒有接口怎麽辦?
後續文章打算講一下 CGLIB 和 Sping Aop 是怎麽為我們打開一個新的世界的。
代理之故事的結局
最後中介小王和李老板的故事結局是怎麽樣的呢?
程序員小H租了房子, 李老板和小王按規定各付中介費1750, 最後小王、李老板和小H商量了一下, 不走公司的流程, 各付1000元就好了。
靜態代理和動態代理