1. 程式人生 > >【設計模式】動態代理模式

【設計模式】動態代理模式

上篇講述了普通代理模式,今天來講講動態代理.說起動態代理,大家可能首先想到的就是Spring的AOP.我們天天在說AOP是通過動態代理實現的,那麼動態代理到底是個什麼呢?看完這篇文章你就會明白,同時也明白AOP到底是哪裡用到了動態代理.

    首先,我們來看動態代理的定義:動態代理是在實現階段不用關心代理誰,而在執行階段才指定代理哪一個物件的一種特殊代理模式。

    接下來還是通過例項來學習吧,假定現有使用者登入需求,在登入時需要校驗驗證碼,校驗驗證碼這個邏輯嚴格來講其實是不屬於登入邏輯的,只是一種額外的防刷校驗,防止使用者通過重複密碼碰撞來實現密碼破解,所以此時我們是不應該把他放進登入邏輯裡,so,此處引入動態代理模式,校驗邏輯交由代理類執行,接下來我們來看動態代理模式類圖:

動態代理模式

    其中InvocationHandler是javareflect包中用於實現動態代理模式的一個核心介面,通過實現它並實現invoke介面來實現我們自己的邏輯.接下來我們來看具體程式碼實現:

    首先基,於抽象化,先定義介面IPlayer:

public interface IPlayer {
    /**
     * 登入
     */
    void login(String captcha);
}

    接下來我們看具體的玩家實現類Player:

public class Player implements IPlayer {
    /** 玩家姓名 */
private String name; public Player(String name) { this.name = name; } @Override public void login(String captcha) { //此處校驗登入密碼等 System.out.println(MessageFormat.format("玩家{0}登入成功!!!!!", name)); } }

    可以看到我們玩家的實現中並不會對圖形驗證碼做處理,只會執行自有登入邏輯.接下來我們來看核心類PlayerHandler:

public class PlayerHandler implements InvocationHandler {

    private Object targetObj;

    /**
     * 代理方法,通過該方法返回具體的代理類
     */
    public Object proxy(Object sourceObj) {
        this.targetObj = sourceObj;
        return Proxy.newProxyInstance(sourceObj.getClass().getClassLoader(), sourceObj.getClass().getInterfaces(),
            this);
    }

    /**
     * 前置包裝方法  此處業務為校驗驗證碼
     */
    private void before(Method method, Object[] args) {
        System.out.println("執行前置方法");
        String captcha = args[0].toString();
        System.out.println(MessageFormat.format("使用者的圖形驗證碼是:{0}", captcha));
        if (!"1111".equals(captcha)) {
            throw new IllegalArgumentException("圖形驗證碼不正確");
        }
    }

    /**
     * 後續包裝方法
     */
    private void after(Method method, Object[] args) {
        System.out.println("執行後續方法");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //執行前置方法
        before(method, args);
        System.out.println(MessageFormat.format("動態代理執行器開始執行{0}方法!", method.getName()));
        //這一步會呼叫代理的具體物件執行具體方法
        Object result = method.invoke(targetObj, args);
        //執行後續方法
        after(method, args);
        return result;
    }
}

    上述程式碼中註釋已經很詳細了,如有不明白的可以留言.此處我們直接來看client呼叫:

public static void main(String[] args) {
    IPlayer player = (IPlayer) new PlayerHandler().proxy(new Player("white"));
    player.login("2222");
}

    執行結果如下:

執行前置方法
使用者的圖形驗證碼是:2222
Exception in thread "main" java.lang.IllegalArgumentException: 圖形驗證碼不正確

    可以看到由於驗證碼並不匹配,所以在before方法中丟擲異常,並未走到使用者校驗登入密碼等邏輯,實現了提前業務處理.

    接下來看驗證碼校驗通過的情況,將login入參改為”1111”,執行結果如下:

執行前置方法
使用者的圖形驗證碼是:1111
動態代理執行器開始執行login方法!
玩家white登入成功!!!!!
執行後續方法

    可以看到校驗通過,使用者登入成功.

    看到這裡不知道大家有沒有想起平時所用的Spring AOP?是否覺得我們平時用的AOP就是這樣的(當然此處沒有引入切面的概念).當然有朋友說了,我們的AOP可以在多個方法上使用,而且不用修改AOP的程式碼,那這裡比如此處如果我們要擴充套件上面的程式,給玩家添加註冊功能,也要校驗驗證碼,我們的代理類也是不需要做任何修改的,只需要在IPlayer和Player中增加regist方法即可實現.

    看到這裡大家是否明白了我們AOP中到底是怎麼運用動態代理模式的了,就是我們寫的AOP作為一個動態代理類,在執行完我們的預處理方法befor後,動態的獲取到我們添加註解的方法所在的物件,之後呼叫我們添加註解的方法,之後再執行我們的後續處理after方法.

總結

  1. 隔離性:動態代理類不關心原有業務邏輯,只關心自己的前後處理邏輯
  2. 擴充套件性:增加其他業務時只要前後處理邏輯相同,不用修改代理類程式碼,只需新增業務介面即可

歡迎關注個人部落格:blog.scarlettbai.com