1. 程式人生 > >靜態代理和動態代理

靜態代理和動態代理

代理模式 操作 改變 final false 為我 什麽 csdn 思考

代理模式(靜態代理)

代理模式是為其他對象提供一種代理以控制對這個對象的訪問。

定義上也不算好理解, 上一個 《大話設計模式》 的圖。

技術分享圖片

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("帶人看房子");
    }

}

代理對象的特征是

  1. 和被代理對象實現了同一個接口;
  2. 內部含有一個被代理對象的示例;

優點

  1. 真實對象可以專註於自己的業務邏輯控制;
  2. 非業務邏輯相關的部分, 可以通過代理類來處理;
  3. 隱藏了真實的對象, 對外只暴露代理對象。
  4. 擴展性:由於實現了相同的接口, 因此被代理對象的邏輯不管如何變化, 代理對象都不需要更改。

使用場合

  1. 控制對真實對象的訪問;
  2. 實現日誌記錄;
  3. 統計對象的訪問數量;
  4. 等等。

一些思考

在代理對象內部, 真實對象是什麽時候被初始化的, 以及初始化的對象是由誰產生的?

在我看過的代碼和書籍中, 有如下幾種, 對此我也是很困惑, 以下是我的一些見解

不推薦

  1. 將真實對象在代理對象構造函數內部初始化出來。
public LandlordProxy() {
    landlord = new LandlordImpl();
}
  1. 構造函數不做改變, 在使用方法時
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元就好了。

靜態代理和動態代理