Spring AOP中的職責(zé)鏈模式

1. 職責(zé)鏈模式(Chain Of Responsibility Design Pattern)

在職責(zé)鏈模式中免猾,多個處理器(也就是剛剛定義中說的“接收對象”)依次處理同一個請求宏榕。一個請求先經(jīng)過 A 處理器處理延蟹,然后再把請求傳遞給 B 處理器榜轿,B 處理器處理完后再傳遞給 C 處理器疼约,以此類推辞州,形成一個鏈條画畅。鏈條上的每個處理器各自承擔(dān)各自的處理職責(zé)砸琅,所以叫作職責(zé)鏈模式。

IHandler是所有處理器類的接口轴踱,handle()方法是抽象方法症脂,每個具體的處理器類(HandlerA、HandlerB)的handle()方法用來處理請求淫僻,HandlerChain是處理器鏈诱篷,代碼如下所示:


public interface IHandler {
  boolean handle();
}

public class HandlerA implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerB implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerChain {
  private List<IHandler> handlers = new ArrayList<>();

  public void addHandler(IHandler handler) {
    this.handlers.add(handler);
  }

  public void handle() {
    for (IHandler handler : handlers) {
      boolean handled = handler.handle();
      if (handled) {
        break;
      }
    }
  }
}

// 使用舉例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

2. Spring AOP的基本使用和源碼解析

Spring AOP的基本使用請參考:SpringAOP
Spring AOP源碼解析請參考:Spring AOP源碼淺析

3.Spring AOP的職責(zé)鏈模式

3.1. MethodInteceptor接口

相當(dāng)于上文中的IHandler,每個處理器都要實現(xiàn)該接口雳灵,其中invoke()方法棕所,是處理器的處理邏輯

public interface MethodInteceptor {

    Object invoke(MethodInvocation mi) throws Throwable;

}
3.2. CglibMethodInvocation

CglibMethodInvocation相當(dāng)于上文中的HandlerChain ,該類中定義了攔截器鏈以及攔截器鏈的執(zhí)行邏輯细办,代碼模擬了Spring AOP的處理

public class CglibMethodInvocation implements MethodInvocation{

    List<MethodInteceptor> inteceptors;
    //當(dāng)前攔截器鏈執(zhí)行的位置橙凳。
    int currentInterceptorIndex = -1;

    //要切入的目標(biāo)類對象蕾殴,方法,方法參數(shù)
    Object target;
    Method method;
    Object[] args;

    CglibMethodInvocation(List<MethodInteceptor> inteceptors, Object target, Method method,Object[] args){
        this.inteceptors = inteceptors;
        this.target = target;
        this.method = method;
        this.args = args;
    }


    @Override
    public Object proceed() throws Throwable {
        //當(dāng)攔截器鏈執(zhí)行完之后岛啸,就執(zhí)行目標(biāo)類的目標(biāo)方法钓觉。
        if (this.currentInterceptorIndex == this.inteceptors.size() - 1) {
            return method.invoke(target,args);
        }

        MethodInteceptor inteceptor = this.inteceptors.get(++currentInterceptorIndex);

        return inteceptor.invoke(this);
    }
}
3.3. MethodInvocation

MethodInvocation是CglibMethodInvocation的抽象。

public interface MethodInvocation {

    Object proceed() throws Throwable;
}
3.4. MethodInteceptor的實現(xiàn)類

MethodInteceptor的實現(xiàn)類就是攔截器的具體實現(xiàn)坚踩,在每個invoke(MethodInvocation mi)方法中荡灾,都會調(diào)用mi.proceed()方法,讓攔截器鏈能夠繼續(xù)往下執(zhí)行瞬铸。

public class MethodBeforeAdviceInterceptor implements MethodInteceptor{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        System.out.println("假裝這里執(zhí)行了切面類@Before標(biāo)注的方法批幌。。嗓节。荧缘。。拦宣。截粗。");
        return mi.proceed();
    }
}

public class AspectJAfterAdvice implements MethodInteceptor{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try{
            return mi.proceed();
        }
        finally {
            System.out.println("假裝這里執(zhí)行了切面類@After標(biāo)注的方法。鸵隧。绸罗。。豆瘫。珊蟀。");
        }
    }
}

public class AfterReturningAdviceInterceptor implements MethodInteceptor{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object returnVal = mi.proceed();
        System.out.println("假裝這里執(zhí)行了切面類@AfterReturning標(biāo)注的方法。外驱。育灸。。略步。描扯。");
        return returnVal;
    }
}

public class AspectJAfterThrowingAdvice implements MethodInteceptor{
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        try{
            return mi.proceed();
        }catch (Throwable throwable){
            System.out.println("假裝執(zhí)行了切面類@AfterThrowing標(biāo)注的方法");
            throw throwable;
        }
    }
}
3.5. TargetClass目標(biāo)類

TargetClass目標(biāo)類就是簡單模擬一下定页,Spring AOP中要被切入的類趟薄。

public class TargetClass {

    public void targetMethod(String s){
        //先簡單拋個異常
        //int i = 1 / 0;
        System.out.println("假裝這里執(zhí)行了目標(biāo)類的目標(biāo)方法,傳參:"+s);
    }
}
3.6. 測試類MainClass
public class MainClass {
    public static void main(String[] args) throws Throwable {
        //初始化攔截器
        List<MethodInteceptor> inteceptors = new ArrayList<>(4);
        inteceptors.add(new AspectJAfterThrowingAdvice());
        inteceptors.add(new AfterReturningAdviceInterceptor());
        inteceptors.add(new AspectJAfterAdvice());
        inteceptors.add(new MethodBeforeAdviceInterceptor());

        //利用反射獲取目標(biāo)類以及目標(biāo)方法,然后初始化參數(shù)
        TargetClass targetClass = new TargetClass();
        Method method = targetClass.getClass().getMethod("targetMethod",String.class);
        Object[] methodArgs = {"我是參數(shù)"};

        //執(zhí)行職責(zé)鏈
        CglibMethodInvocation methodInvocation = new CglibMethodInvocation(inteceptors,targetClass,method,methodArgs);
        methodInvocation.proceed();
    }
}
3.7. 測試結(jié)果

先注掉TargetClass中的int i = 1 / 0:

假裝這里執(zhí)行了切面類@Before標(biāo)注的方法典徊。杭煎。。卒落。羡铲。。儡毕。
假裝這里執(zhí)行了目標(biāo)類的目標(biāo)方法,傳參:我是參數(shù)
假裝這里執(zhí)行了切面類@After標(biāo)注的方法也切。扑媚。。雷恃。疆股。。
假裝這里執(zhí)行了切面類@AfterReturning標(biāo)注的方法倒槐。旬痹。。讨越。两残。。

Process finished with exit code 0

放開注釋把跨,讓目標(biāo)方法拋個異常:

假裝這里執(zhí)行了切面類@Before標(biāo)注的方法人弓。。着逐。票从。。滨嘱。峰鄙。
假裝這里執(zhí)行了切面類@After標(biāo)注的方法。太雨。吟榴。。囊扳。吩翻。
假裝執(zhí)行了切面類@AfterThrowing標(biāo)注的方法
Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.ljessie.designpattern.behavior.chain.CglibMethodInvocation.proceed(CglibMethodInvocation.java:25)
    at com.ljessie.designpattern.behavior.chain.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:7)
    at com.ljessie.designpattern.behavior.chain.CglibMethodInvocation.proceed(CglibMethodInvocation.java:30)
    at com.ljessie.designpattern.behavior.chain.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:7)
    at com.ljessie.designpattern.behavior.chain.CglibMethodInvocation.proceed(CglibMethodInvocation.java:30)
    at com.ljessie.designpattern.behavior.chain.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:6)
    at com.ljessie.designpattern.behavior.chain.CglibMethodInvocation.proceed(CglibMethodInvocation.java:30)
    at com.ljessie.designpattern.behavior.chain.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:7)
    at com.ljessie.designpattern.behavior.chain.CglibMethodInvocation.proceed(CglibMethodInvocation.java:30)
    at com.ljessie.designpattern.behavior.chain.MainClass.main(MainClass.java:23)
Caused by: java.lang.ArithmeticException: / by zero
    at com.ljessie.designpattern.behavior.chain.TargetClass.targetMethod(TargetClass.java:9)
    ... 14 more

Process finished with exit code 1

到這里,攔截器鏈的執(zhí)行邏輯與Spring AOP基本相符锥咸。在目標(biāo)方法沒有拋出異常時狭瞎,會走AfterReturning方法;拋出異常時搏予,會走AfterThrowing方法熊锭。

4. 總結(jié)

本文從Spring AOP的源碼中,抽取出來職責(zé)鏈模式相關(guān)代碼雪侥,簡單介紹了下Spring AOP中職責(zé)鏈模式的應(yīng)用碗殷。

在Spring AOP中,MethodInterceptor是所有攔截器的抽象接口速缨,然后各個攔截器實現(xiàn)了MethodInterceptor接口锌妻。

CglibMethodInvocation是中的proceed()方法,定義了攔截器鏈的處理邏輯旬牲,當(dāng)所有攔截器都執(zhí)行完畢之后仿粹,開始執(zhí)行目標(biāo)類的目標(biāo)方法搁吓。MethodInvocation接口是CglibMethodInvocation的抽象。

如有分析不當(dāng)之處吭历,還煩請各位看官多多指正擎浴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市毒涧,隨后出現(xiàn)的幾起案子贮预,更是在濱河造成了極大的恐慌,老刑警劉巖契讲,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仿吞,死亡現(xiàn)場離奇詭異,居然都是意外死亡捡偏,警方通過查閱死者的電腦和手機唤冈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來银伟,“玉大人你虹,你說我怎么就攤上這事⊥埽” “怎么了傅物?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琉预。 經(jīng)常有香客問我董饰,道長,這世上最難降的妖魔是什么圆米? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任卒暂,我火速辦了婚禮,結(jié)果婚禮上娄帖,老公的妹妹穿的比我還像新娘也祠。我一直安慰自己,他們只是感情好近速,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布诈嘿。 她就那樣靜靜地躺著,像睡著了一般数焊。 火紅的嫁衣襯著肌膚如雪永淌。 梳的紋絲不亂的頭發(fā)上崎场,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天佩耳,我揣著相機與錄音,去河邊找鬼谭跨。 笑死干厚,一個胖子當(dāng)著我的面吹牛李滴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛮瞄,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼所坯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挂捅?” 一聲冷哼從身側(cè)響起芹助,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闲先,沒想到半個月后状土,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡伺糠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年蒙谓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片训桶。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡累驮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舵揭,到底是詐尸還是另有隱情谤专,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布午绳,位于F島的核電站毒租,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箱叁。R本人自食惡果不足惜墅垮,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耕漱。 院中可真熱鬧算色,春花似錦、人聲如沸螟够。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妓笙。三九已至若河,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寞宫,已是汗流浹背萧福。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辈赋,地道東北人鲫忍。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓膏燕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悟民。 傳聞我的和親對象是個殘疾皇子坝辫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348