Spring AOP (四) 多重代理和責(zé)任鏈模式

前面我們講 JDK 動態(tài)代理和 CGLIB 動態(tài)代理時,都只說了一次代理,即對目標(biāo)方法做一次增強操作鸵赫。

下面我們來看看如何用 JDK 動態(tài)代理如何實現(xiàn)多重代理。

嵌套代理對象

本文中的代碼用到 Lombok 注解躏升,因此需要引入下面的 Maven 依賴辩棒。

<!--
    Lombok能通過注解的方式,在編譯時自動為屬性生成構(gòu)造器膨疏、getter/setter一睁、equals、hashcode佃却、toString 等方法
    如果需要使用者吁,還需要在 IDE 中安裝 lombok 插件,安裝步驟請百度
-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
    <scope>provided</scope>
</dependency>

首先饲帅,我們來修改一下 Spring AOP (二) JDK 動態(tài)代理 中提到的 JdkDynamicProxy 類复凳。

@Data
public class JdkDynamicProxy1 implements InvocationHandler {
    /**
     *  目標(biāo)對象(也被稱為被代理對象)
     *
     *      Java 代理模式的一個必要要素就是代理對象要能拿到被代理對象的引用
     */
    private Object target;

    /**
     * 用來標(biāo)識 InvocationHandler invoke 調(diào)用
     */
    private String tag;

    public JdkDynamicProxy1(Object target){
        this.target=target;
    }

    /**
     * 回調(diào)方法
     * @param proxy JDK 生成的代理對象
     * @param method 被代理的方法(也就是需要增強的方法)
     * @param args  被代理方法的參數(shù)
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 直接輸出 hashCode 值,避免 invoke 遞歸調(diào)用灶泵,導(dǎo)致棧溢出
        if (method.getName().equals("hashCode")) {
            return hashCode();
        }

        System.out.println("JdkDynamicProxy1 invoke 方法執(zhí)行前---------------" + info(proxy));
        Object object= method.invoke(this.target, args);
        System.out.println("JdkDynamicProxy1 invoke 方法執(zhí)行后----------------" + info(proxy));
        return object;
    }

    /**
     * 輸出代理類的一些信息育八,比如類名,hashCode 等
     * @param proxy
     * @return
     */
    private String info(Object proxy) {
        return proxy.getClass().getName() + ":" + proxy.hashCode() + "----------------" + this.tag;
    }

    /**
     * 獲取被代理接口實例對象
     *
     *      通過 Proxy.newProxyInstance 可以獲得一個代理對象赦邻,它實現(xiàn)了 target.getClass().getInterfaces() 接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}

JdkDynamicProxy1 類中新增了 tag 字段髓棋,用于標(biāo)識 invoke 方法調(diào)用中的代理類,并在 invoke 方法調(diào)用中打印了代理類的類名hashCode 值深纲。

Client1 類中的測試代碼仲锄,通過多個代理對象嵌套的方式實現(xiàn)了多重代理。

public class Client1 {
    public static void main(String[] args) {
        // 1. 構(gòu)造目標(biāo)對象
        Cat catTarget = new Cat();

        // 2. 根據(jù)目標(biāo)對象生成代理對象
        JdkDynamicProxy1 proxy = new JdkDynamicProxy1(catTarget);
        proxy.setTag("第一個代理類");

        // JDK 動態(tài)代理是基于接口的湃鹊,所以只能轉(zhuǎn)換為 Cat 實現(xiàn)的接口 Animal
        Animal catProxy = proxy.getProxy();

        // 3. 根據(jù)第一個代理對象生成代理對象
        JdkDynamicProxy1 proxy2 = new JdkDynamicProxy1(catProxy);
        proxy2.setTag("第二個代理類");

        // JDK 動態(tài)代理是基于接口的儒喊,所以只能轉(zhuǎn)換為 Cat 實現(xiàn)的接口 Animal
        Animal catProxy2 = proxy2.getProxy();

        // 調(diào)用代理對象的方法
        catProxy2.eat();
    }
}

運行結(jié)果如下所示。

輸出結(jié)果.jpg

嵌套代理對象的結(jié)構(gòu)示意圖如下所示币呵。

嵌套代理對象的結(jié)構(gòu)示意圖.jpg

這樣怀愧,我們就通過嵌套代理對象的方式實現(xiàn)了多重代理。

責(zé)任鏈模式一

首先余赢,我們定義一個 AbstractHandler 類芯义。

public abstract class AbstractHandler {

    /**
     * 責(zé)任鏈中下一個處理者
     */
    @Setter
    private AbstractHandler nextHandler;

    /**
     * 是否有下一個處理者
     * @return
     */
    public boolean hasNextHandler() {
        return this.nextHandler != null;
    }

    abstract Object invoke(TargetMethod targetMethod) throws Throwable;


    public final Object proceed(TargetMethod targetMethod) throws Throwable {
        // 如果沒有下一個處理者,則直接調(diào)用目標(biāo)對象的被代理方法
        if (!hasNextHandler()) {
           return targetMethod.getMethod().invoke(targetMethod.getTarget(), targetMethod.getArgs());
        }

        // 否則調(diào)用下一個處理者的方法
        return this.nextHandler.invoke(targetMethod);
    }

    /**
     *  第一個 Handler妻柒,不做額外處理扛拨,起驅(qū)動責(zé)任鏈向前調(diào)用的作用
     */
    public static class HeadHandler extends AbstractHandler {

        @Override
        Object invoke(TargetMethod targetMethod) throws Throwable {
            return null;
        }
    }
}

AbstractHandler 類中有兩個核心方法,proceed 用來驅(qū)動責(zé)任鏈條向前執(zhí)行举塔,invoke 用來做目標(biāo)方法增強處理绑警。

然后再來修改一下 Spring AOP (二) JDK 動態(tài)代理 中提到的 JdkDynamicProxy 類。

@AllArgsConstructor
public class JdkDynamicProxy2 implements InvocationHandler {

    /**
     * 目標(biāo)對象(也被稱為被代理對象)
     */
    private Object target;

    /**
     * 責(zé)任鏈 HeadHandler央渣,僅用來開始責(zé)任鏈執(zhí)行计盒,不對方法進行增強處理
     */
    private AbstractHandler.HeadHandler headHandler;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        TargetMethod targetMethod = new TargetMethod();
        targetMethod.setTarget(target);
        targetMethod.setMethod(method);
        targetMethod.setArgs(args);

        return headHandler.proceed(targetMethod);
    }

    /**
     * 獲取被代理接口實例對象
     *
     *      通過 Proxy.newProxyInstance 可以獲得一個代理對象,它實現(xiàn)了 target.getClass().getInterfaces() 接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}

JdkDynamicProxy2 類中新增了 headHandler 字段芽丹,用來驅(qū)動責(zé)任鏈的執(zhí)行北启。

通過 Client2 類中的測試代碼,可以看到責(zé)任鏈模式是如何實現(xiàn)多重代理拔第。

public class Client2 {

    public static void main(String[] args) {
        // 1. 構(gòu)造目標(biāo)對象
        Cat catTarget = new Cat();

        // 2. 構(gòu)造 Handler 對象
        AbstractHandler.HeadHandler headHandler = new AbstractHandler.HeadHandler();

        AbstractHandler handler1 = new Handler1();
        headHandler.setNextHandler(handler1);

        Handler2 handler2 = new Handler2();
        handler1.setNextHandler(handler2);

        // 3. 根據(jù)目標(biāo)對象生成代理對象
        JdkDynamicProxy2 proxy = new JdkDynamicProxy2(catTarget, headHandler);

        // JDK 動態(tài)代理是基于接口的咕村,所以只能轉(zhuǎn)換為 Cat 實現(xiàn)的接口 Animal
        Animal catProxy = proxy.getProxy();

        // 調(diào)用代理對象的方法
        catProxy.eat();
    }

    private static class Handler1 extends AbstractHandler {

        @Override
        Object invoke(TargetMethod targetMethod) throws Throwable {
            System.out.println("Handler1 處理開始-------------------------------");

            Object ret = super.proceed(targetMethod);

            System.out.println("Handler1 處理完成-------------------------------");
            return ret;
        }
    }

    private static class Handler2 extends AbstractHandler {

        @Override
        Object invoke(TargetMethod targetMethod) throws Throwable {
            System.out.println("Handler2 處理開始-------------------------------");

            Object ret = super.proceed(targetMethod);

            System.out.println("Handler2 處理完成-------------------------------");
            return ret;
        }
    }
}

運行結(jié)果如下所示。

輸出結(jié)果.jpg

責(zé)任鏈模式一結(jié)構(gòu)示意圖如下所示楼肪。

責(zé)任鏈模式一結(jié)構(gòu)示意圖.png

這樣培廓,我們就通過責(zé)任鏈模式實現(xiàn)了多重代理。

責(zé)任鏈模式二

上面的責(zé)任鏈模式有些許不足之處春叫,比如需要一個 Head 對象驅(qū)動責(zé)任鏈模式的運行肩钠,鏈表結(jié)構(gòu)不易調(diào)整等等。

因此暂殖,我們通過數(shù)組的方式來改進責(zé)任鏈模式下的多重代理价匠。

首先,我們定義兩個接口呛每,MyMethodInvocationMyMethodInterceptor踩窖。

public interface MyMethodInvocation {

    /**
     * 進入攔截器鏈中的下一個攔截器,驅(qū)動責(zé)任鏈模式向前運行
     */
    Object proceed() throws Throwable;
}

/**
 *  在到達目標(biāo)方法之前攔截對方法的調(diào)用晨横。
 *
 * @Author: wilimm
 * @Date: 2019/5/4 14:16
 */
public interface MyMethodInterceptor {

    /**
     * 類似于 InvocationHandler 的 invoke 方法洋腮,用于對方法做增強處理箫柳,并通過 invocation 參數(shù)驅(qū)動責(zé)任鏈向前運行
     * @param invocation
     * @return
     * @throws Throwable
     */
    Object invoke(MyMethodInvocation invocation) throws Throwable;
}

然后我們定義 MyMethodInvocationImpl 類,用來實現(xiàn) MyMethodInvocation 接口啥供。

@Data
public class MyMethodInvocationImpl implements MyMethodInvocation {

    /**
     * 攔截器鏈
     */
    private List<MyMethodInterceptor> interceptorList;

    /**
     * 被代理的目標(biāo)方法
     */
    private TargetMethod targetMethod;

    /**
     * 當(dāng)前調(diào)用的攔截器索引
     */
    private int currentInterceptorIndex = 0;

    @Override
    public Object proceed() throws Throwable {
        /**
         *  索引值從 0 開始遞增悯恍,所以如果 currentInterceptorIndex 等于攔截器集合大小,說明所有的攔截器都執(zhí)行完畢了
         */
        if (this.currentInterceptorIndex == this.interceptorList.size()) {
            // 調(diào)用目標(biāo)方法
            return targetMethod.getMethod().invoke(targetMethod.getTarget(), targetMethod.getArgs());
        }

        // 獲取下一個攔截器伙狐,并調(diào)用其 invoke 方法
        MyMethodInterceptor methodInterceptor =
                this.interceptorList.get(this.currentInterceptorIndex++);

        return methodInterceptor.invoke(this);
    }
}

前期準(zhǔn)備工作做完之后涮毫,我們可以來改造 Spring AOP (二) JDK 動態(tài)代理 中提到的 JdkDynamicProxy 類了。

@Data
public class JdkDynamicProxy3 implements InvocationHandler {

    /**
     * 目標(biāo)對象(也被稱為被代理對象)
     */
    private Object target;

    /**
     * 攔截器鏈
     */
    private List<MyMethodInterceptor> interceptorList = new ArrayList<>();

    public void addMethodInterceptor(MyMethodInterceptor methodInterceptor) {
        this.interceptorList.add(methodInterceptor);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        TargetMethod targetMethod = new TargetMethod();
        targetMethod.setTarget(target);
        targetMethod.setMethod(method);
        targetMethod.setArgs(args);

        MyMethodInvocationImpl methodInvocation = new MyMethodInvocationImpl();
        methodInvocation.setTargetMethod(targetMethod);
        methodInvocation.setInterceptorList(interceptorList);

        return methodInvocation.proceed();
    }

    /**
     * 獲取被代理接口實例對象
     *
     *      通過 Proxy.newProxyInstance 可以獲得一個代理對象贷屎,它實現(xiàn)了 target.getClass().getInterfaces() 接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
}

JdkDynamicProxy3 類中新增了 interceptorList 字段罢防,并用 MyMethodInvocationImpl 類來驅(qū)動責(zé)任鏈的執(zhí)行。

通過 Client3 類中的測試代碼唉侄,可以看到改進后的責(zé)任鏈模式比之前 Client2 中的責(zé)任鏈模式的使用優(yōu)雅了很多咒吐。

public class Client3 {

    public static void main(String[] args) {
        // 1. 構(gòu)造目標(biāo)對象
        Cat catTarget = new Cat();

        // 2. 根據(jù)目標(biāo)對象生成代理對象
        JdkDynamicProxy3 proxy = new JdkDynamicProxy3();
        proxy.setTarget(catTarget);

        // 3. 添加方法攔截器
        proxy.addMethodInterceptor(new MethodInterceptor1());
        proxy.addMethodInterceptor(new MethodInterceptor2());

        // JDK 動態(tài)代理是基于接口的,所以只能轉(zhuǎn)換為 Cat 實現(xiàn)的接口 Animal
        Animal catProxy = proxy.getProxy();

        // 調(diào)用代理對象的方法
        catProxy.eat();
    }

    private static class MethodInterceptor1 implements MyMethodInterceptor {
        @Override
        public Object invoke(MyMethodInvocation invocation) throws Throwable {
            System.out.println("MethodInterceptor1 處理開始-------------------------------");

            Object ret = invocation.proceed();

            System.out.println("MethodInterceptor1 處理完成-------------------------------");
            return ret;
        }
    }

    private static class MethodInterceptor2 implements MyMethodInterceptor {
        @Override
        public Object invoke(MyMethodInvocation invocation) throws Throwable {
            System.out.println("MethodInterceptor2 處理開始-------------------------------");

            Object ret = invocation.proceed();

            System.out.println("MethodInterceptor2 處理完成-------------------------------");
            return ret;
        }
    }
}

運行結(jié)果如下所示美旧。

輸出結(jié)果.jpg

責(zé)任鏈模式二結(jié)構(gòu)示意圖如下所示渤滞。

責(zé)任鏈模式二結(jié)構(gòu)示意圖.png

通過上面的內(nèi)容,我們已經(jīng)了解了如何通過嵌套代理對象責(zé)任鏈模式實現(xiàn)多重代理榴嗅。

(正文完)

本文所用代碼地址

擴展閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妄呕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子嗽测,更是在濱河造成了極大的恐慌绪励,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唠粥,死亡現(xiàn)場離奇詭異疏魏,居然都是意外死亡,警方通過查閱死者的電腦和手機晤愧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門大莫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人官份,你說我怎么就攤上這事只厘。” “怎么了舅巷?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵羔味,是天一觀的道長。 經(jīng)常有香客問我钠右,道長赋元,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮搁凸,結(jié)果婚禮上媚值,老公的妹妹穿的比我還像新娘。我一直安慰自己护糖,他們只是感情好杂腰,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著椅文,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惜颇。 梳的紋絲不亂的頭發(fā)上皆刺,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音凌摄,去河邊找鬼羡蛾。 笑死,一個胖子當(dāng)著我的面吹牛锨亏,可吹牛的內(nèi)容都是我干的痴怨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼器予,長吁一口氣:“原來是場噩夢啊……” “哼浪藻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乾翔,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爱葵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后反浓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萌丈,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年雷则,在試婚紗的時候發(fā)現(xiàn)自己被綠了辆雾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡月劈,死狀恐怖度迂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艺栈,我是刑警寧澤英岭,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站湿右,受9級特大地震影響诅妹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一吭狡、第九天 我趴在偏房一處隱蔽的房頂上張望尖殃。 院中可真熱鬧,春花似錦划煮、人聲如沸送丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽器躏。三九已至,卻和暖如春蟹略,著一層夾襖步出監(jiān)牢的瞬間登失,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工挖炬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留揽浙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓意敛,卻偏偏與公主長得像馅巷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子草姻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容