從頭開始瞭解學習攔截器、認識責任鏈模式

由於動態代理之前介紹過的(JDK動態代理CGLIB動態代理)在實際開發一般比較難理解,程序設計者會設計一個攔截器接口供考法這使用,開發者只需知道攔截器接口的方法,含義和作用即可,無需 知道動態代理是怎麼實現的,用JDK動態代理來實現一個攔截器的邏輯,爲此先定義攔截器接口,代碼如下:

定義攔截器接口
/**
 * @author AmVilCres 
 * <p>
 *  該接口定義了3個方法:before、around、after
 *  <li>3個方法的參數爲:proxy代理對象、target真實的對象、method方法、args運行方法參數</li>
 *  <li>before方法返回boolean值,它在真實對象前調用。當返回true時,則反射真實對象的方法;當返回false時,則調用around方法</li>
 *  <li>在反射真實對象方法或者around方法執行之後,調用after方法</li>
 * </p>
 */
public interface Interceptor {
    public boolean before(Object proxy, Object target, Method method, Object[] args);
    public void around(Object proxy, Object target, Method method, Object[] args);
    public void after(Object proxy, Object target, Method method, Object[] args);
}
接口的上面註釋已經寫得很明白了,相信大家都能看懂,接下來寫一個接口的實現類:
public class MyInterceptor implements Interceptor {

    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.err.println("反射方法前邏輯");
        return false;  //不反射被代理對象的原有方法
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代了被代理對象的方法");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法後邏輯");
    }
}

使用JDK動態代理,就可以去實現這些方法在適當時調用的邏輯了,JDK動態代理中使用攔截器代碼如下:

public class InterceptorJdkProxy implements InvocationHandler{
    private Object target; //真實對象
    private String interceptorClass; // 攔截器全限定名

    public InterceptorJdkProxy(Object target,String interceptorClass) {
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /**
     * 綁定委託對象並返回一個 【代理佔位】
     * 
     * @param target 真實的對象
     * @return 代理對象【佔位】
     * */
    public static Object bind(Object target, String interceptorClass) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), new InterceptorJdkProxy(target, interceptorClass));
    }

    /**
     * 通過代理對象調用方法,首先進入的是這個方法
     * 
     * @param proxy 代理對象
     * @param method  被調用的方法
     * @param args 方法的參數
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass == null)
            return method.invoke(target, args);
        Object res = null;

        //通過反射生成攔截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();

        if(interceptor.before(proxy, target, method, args))
            res = method.invoke(target, args); //反射原有對象方法
        else {
            interceptor.around(proxy, target, method, args);
        }
        interceptor.after(proxy, target, method, args);

        return res;
    }
}

    代碼說明:這裏有兩個屬性,一個是target,它是真實的對象;另一個是字符串interceptorClass,它是一個類的全限定名
     執行步驟:
      1. 在bind方法中用JDK動態代理綁定一個對象,然後返回代理對象
      2. 如果沒有設置攔截器,則直接反射真實對象的方法,然後結束,否則進行第3步,
      3. 通過反射生成攔截器,並準備使用它
      4. 調用攔截器的before方法,如果返回true,反射原來的方法,否則運行攔截器的around方法
      5. 調用攔截的after方法
      6. 返回結果

攔截器工作流程圖:
攔截器工作流程圖
測試代碼:

public class TestInterceptorJdkProxy {
    public static void main(String[] args) {
        // "com.avc.interceptor.MyInterceptor"我定義的爲攔截器的全限定名
        HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(), "com.avc.interceptor.MyInterceptor");
        proxy.sayHello();
    }
}

運行結果:
這裏寫圖片描述

責任鏈模式

上面討論了設計者可能會用攔截器去代替動態代理,然後將接口提供給開發者,從而簡化開發者難度,但是攔截器可能有多個。舉個栗子, 某人需要請假一週,如果把請假申請單看成一個對象,那麼他需要經過項目經理、部門經理、人事等多個角色的審批,每個角色都有機會通過攔截這個申請進行審批或者修改。這個時候就要考慮提供這3個角色的處理邏輯,所以需要提供3個攔截器,而傳遞的則是請假申請單 如圖:
請假示例
當一個對象在一條鏈被多個攔截器攔截處理(攔截器也可以選擇不攔截他)時,就把這樣的設計模式稱爲責任鏈模式,它用於一個對象在多個角色中傳遞的場景。還是剛纔的例子,申請單走到項目經理那,可能吧時間改爲5天,從而影響了後面的審批,後面的審批都要根據前面的結果進行。這個時候考慮用層層代理來實現,就是當申請單(target)走到項目經理處,使用第一個動態代理proxy1,走到部門經理處,部門經理會得到一個在項目經理的代理proxy1基礎上生成的proxy2,當走到人事的時候,會在proxy2的及出生成proxy3,如果還有其他角色,以此類推,下圖描述會更清晰:
這裏寫圖片描述
定義3個攔截器(實現上面定義的接口)

攔截器1public class Interceptor1 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  1 的 before方法");
        return true;
    }
    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  1 的 around方法");
    }
    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  1 的 after方法");
    }
}
----------------------------------
攔截器2:
public class Interceptor2 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  2 的 before方法");
        return true;
    }
    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  2 的 around方法");
    }
    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  2 的 after方法");
    }
}
---------------------------------------
攔截器3public class Interceptor3 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  3 的 before方法");
        return true;
    }
    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  3 的 around方法");
    }
    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("攔截器  3 的 after方法");
    }
}

測試類:
public class LinkInterceptorTest {

    public static void main(String[] args) {
        HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(), "com.avc.interceptor.link.Interceptor1");
        HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1, "com.avc.interceptor.link.Interceptor2");
        HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2, "com.avc.interceptor.link.Interceptor3");
        proxy3.sayHello();
    }
}

運行結果:
結果
可以通過改變before方法的返回值,從而得到不同的結果!
代碼下載:上述代碼的源碼,導入eclipse即可運行,有問題請留言

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章