Spring @ExceptionHandler 運(yùn)行原理分析

關(guān)鍵詞: JAVA, Spring, SpringMVC

spring exception handler因使用簡(jiǎn)單怀吻,代碼簡(jiǎn)介,優(yōu)雅榛鼎,廣受開(kāi)發(fā)人員喜愛(ài)鲫惶。
使用出來(lái)的效果如下:


image.png

(圖是盜的,如何使用 不是本文討論的重點(diǎn)岗憋。)

此處就來(lái) 揭示下其工作原理肃晚。

ExceptionHandler 的運(yùn)行效果有 以下特點(diǎn)。

  1. 一次聲明仔戈,全接口生效关串。
  2. Exception的匹配 符合" 就近原則 "

現(xiàn)在我們就來(lái)依次探尋。

首先是 第一條监徘。

眾所周知的是晋修,springMVC 基于servelet ,Spring mvc 的 總?cè)肟?就是凰盔。 org.springframework.web.servlet.DispatcherServlet墓卦。

異常捕獲的偽代碼為

                        dispatchException=null;
            try {
                response = handler(request);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

spring 所有 映射接口 的 入口 從 DispatcherServlet 進(jìn)入,在執(zhí)行的時(shí)候 會(huì) 捕獲所有異常
需要注意的是户敬,僅捕獲 Exception落剪。 所有的Error 將被 NestedServletException 進(jìn)行包裝
包裝為 NestedServletException睁本。所以平時(shí)開(kāi)發(fā)的時(shí)候 應(yīng)當(dāng)注意處理 spring的 NestedServletException。

比較神奇的 是第二條忠怖。
Exception的匹配 符合" 就近原則 "呢堰。

平時(shí)我們開(kāi)發(fā)的時(shí)候,一個(gè)try凡泣,寫(xiě)連續(xù)的多個(gè) catch枉疼。catch處理塊的 編寫(xiě)順序應(yīng)當(dāng)是 先寫(xiě)子類(lèi)后寫(xiě)父類(lèi)。
用來(lái)讓錯(cuò)誤被正確的解析鞋拟。
平時(shí) catch 是否匹配的 邏輯 在于往衷,根據(jù)先后順序,依次向下匹配严卖,當(dāng)拋出的異常 為catch 聲明的 異常的 子類(lèi)或類(lèi)型相同,則認(rèn)為匹配 布轿。

spring 是怎么處理的哮笆?

遍地的 exception,以及 各種 exceptionHandler汰扭,是怎么做到 一處聲明 到處處理稠肘。還能遵從子類(lèi)優(yōu)先原則。

簡(jiǎn)單的想法是萝毛,將所有的 exception 串起來(lái)项阴,寫(xiě)一個(gè)長(zhǎng)長(zhǎng)的 catch。如

try{
 //do
}catch(E1){

}
catch(E1){

}
catch(E2){

}
catch(E3){

}
catch(E4){

}
catch(E5){

}

那么問(wèn)題來(lái)了笆包,
1 這個(gè)順序 怎么定环揽。
2 我們必然需要將要處理的所有異常類(lèi)型放在 一個(gè) 集合中,遍歷后 catch庵佣。顯然 java 并沒(méi)有優(yōu)雅的提供這種循環(huán)catch的操作歉胶。而且比較傻。

我們的異常通常會(huì)進(jìn)行分類(lèi) EA EB巴粪。 過(guò)一段時(shí)間 又對(duì) EA 擴(kuò)展為 EA1 EA2 EA3 通今。對(duì)EB 也擴(kuò)展出了EB2.
EA 為 EA1 EA2 EA3 的父類(lèi)。
當(dāng)異常發(fā)生的 時(shí)候(如EA1)肛根,按照上述邏輯辫塌,一個(gè)異常 要 去 EA EA1 EA2 EA3 EB EB2等所有 catch塊都進(jìn)行一次匹配,尋找正確的處理器派哲。
實(shí)際上 EA1 的處理器臼氨,只可能 被 EA1 與 EA 的異常處理器 所解析。其他的遍歷 顯然是 不必要的狮辽。
同時(shí) 處理的 時(shí)候一也,應(yīng)當(dāng)必須 EA1的處理器被先匹配巢寡。

那應(yīng)當(dāng)怎樣操作?

最好的方式 當(dāng)然是椰苟,直接一個(gè) hashMap key為異常類(lèi)型抑月,value 是 異常的處理程序。
EA1發(fā)生時(shí)舆蝴,直接根據(jù)map找到 EA1的異常處理程序谦絮。
現(xiàn)實(shí)生活顯然不是這樣的 因?yàn)?EA1 可能沒(méi)有自己的專屬 異常處理程序。那么就要找到一個(gè)最近的 EA的異常處理程序洁仗。

這個(gè)算法具體出來(lái)就是這個(gè)核心算法层皱;

    private final Map<Class<? extends Throwable>, Method> mappedMethods =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);

    private final Map<Class<? extends Throwable>, Method> exceptionLookupCache =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
    /**
     * Find a {@link Method} to handle the given exception type. This can be
     * useful if an {@link Exception} instance is not available (e.g. for tools).
     * @param exceptionType the exception type
     * @return a Method to handle the exception, or {@code null} if none found
     */
    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        Method method = this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            method = getMappedMethod(exceptionType);
            this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND));
        }
        return (method != NO_METHOD_FOUND ? method : null);
    }

    /**
     * Return the {@link Method} mapped to the given exception type, or {@code null} if none.
     */
    private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
        List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
        for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
            if (mappedException.isAssignableFrom(exceptionType)) {
                matches.add(mappedException);
            }
        }
        if (!matches.isEmpty()) {
            Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
            return this.mappedMethods.get(matches.get(0));
        }
        else {
            return null;
        }
    }

首先程序會(huì)在一開(kāi)始 加載的時(shí)候?qū)⑺械漠惓L幚矸椒⊕呙杼砑又? mappedMethods 中。所有的赠潦。
當(dāng)一個(gè)新的 需要處理的異常 來(lái)的 時(shí)候 叫胖。
首先從剛才的 mappedMethods 這個(gè) map 中 全部遍歷一遍 ,要求是 isAssignableFrom她奥。就是把能處理此異常的 異常處理程序篩選出來(lái)瓮增。
將篩選后的結(jié)果 放入list中,然后根據(jù)特定的排序器排序哩俭,取第一個(gè) 異常處理器绷跑。
得到后,將此次結(jié)果 放入exceptionLookupCache凡资。下一次 當(dāng)有一樣的異常 來(lái)了的 時(shí)候砸捏,就直接從這個(gè) map 中找到對(duì)應(yīng)的異常處理程序。

那么排序又是怎么實(shí)現(xiàn)的呢隙赁?想來(lái)垦藏,應(yīng)該是子類(lèi)優(yōu)先的機(jī)制。因?yàn)榍懊?的 list 都是 有父子關(guān)系的 異常鸳谜,所有理論上只要根據(jù)子類(lèi)優(yōu)先的排序方法膝藕,子類(lèi)在前,父類(lèi)在后咐扭,然后取第一個(gè)芭挽,自然是 “最近原則” 所匹配的異常處理程序。

實(shí)際代碼要相對(duì)復(fù)雜一些蝗肪,如下

    @Override
    public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
        int depth1 = getDepth(o1, this.targetException, 0);
        int depth2 = getDepth(o2, this.targetException, 0);
        return (depth1 - depth2);
    }

    private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
        if (exceptionToMatch.equals(declaredException)) {
            // Found it!
            return depth;
        }
        // If we've gone as far as we can go and haven't found it...
        if (exceptionToMatch == Throwable.class) {
            return Integer.MAX_VALUE;
        }
        return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袜爪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子薛闪,更是在濱河造成了極大的恐慌辛馆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昙篙,居然都是意外死亡腊状,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)苔可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缴挖,“玉大人,你說(shuō)我怎么就攤上這事焚辅∮澄荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵同蜻,是天一觀的道長(zhǎng)棚点。 經(jīng)常有香客問(wèn)我,道長(zhǎng)湾蔓,這世上最難降的妖魔是什么瘫析? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮默责,結(jié)果婚禮上颁股,老公的妹妹穿的比我還像新娘。我一直安慰自己傻丝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布诉儒。 她就那樣靜靜地躺著葡缰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忱反。 梳的紋絲不亂的頭發(fā)上泛释,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音温算,去河邊找鬼怜校。 笑死,一個(gè)胖子當(dāng)著我的面吹牛注竿,可吹牛的內(nèi)容都是我干的茄茁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼巩割,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼裙顽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起宣谈,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤愈犹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后闻丑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體漩怎,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勋颖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勋锤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饭玲。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怪得,靈堂內(nèi)的尸體忽然破棺而出咱枉,到底是詐尸還是另有隱情,我是刑警寧澤徒恋,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布蚕断,位于F島的核電站,受9級(jí)特大地震影響入挣,放射性物質(zhì)發(fā)生泄漏亿乳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一径筏、第九天 我趴在偏房一處隱蔽的房頂上張望葛假。 院中可真熱鬧,春花似錦滋恬、人聲如沸聊训。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)带斑。三九已至,卻和暖如春勋拟,著一層夾襖步出監(jiān)牢的瞬間勋磕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工敢靡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挂滓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓啸胧,卻偏偏與公主長(zhǎng)得像赶站,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纺念,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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