Spring MVC 全局異常處理-RESTAPI接口返回統(tǒng)一JSON格式-自定義異常處理--404異常捕捉

寫(xiě)之前大概兩周草草的將一些代碼保存在草稿箱两曼,今天有空來(lái)看钠怯,結(jié)果都沒(méi)有了【怨念】---重新整理一下了 -----【轉(zhuǎn)載請(qǐng)標(biāo)注出處

  • 第一部分:需求
  • 第二部分:實(shí)現(xiàn)方式
  • 第三部分:404異常捕捉不能實(shí)現(xiàn)分析
  • 第四部分:原因和源碼分析
  • 第五部分:最終總結(jié)

需求

  • **本意是想針對(duì)對(duì)外REST接口的返回格式進(jìn)行統(tǒng)一,結(jié)果404錯(cuò)誤始終無(wú)法被捕捉 **

實(shí)現(xiàn)方式

對(duì)于目前的主流方式全部嘗試了一遍凭迹,主要有三種罚屋,還有其他一些異類采用Filter之類的方式實(shí)現(xiàn),如需可以自取

**1. SimpleMappingExceptionResolver **
采用xml配置方式嗅绸,代碼零入侵 脾猛。自由性不足,獲取異常信息少(只有異常信息)朽砰。
2. HandlerExceptionResolver
自定義類實(shí)現(xiàn)該類尖滚。可以實(shí)現(xiàn)統(tǒng)一異常處理里瞧柔。我在實(shí)現(xiàn)的時(shí)候卻沒(méi)有生效漆弄,而是走了其自定義的DefaultHandlerExceptionResolver,具體原因下面分析
3. @ExceptionHandler注解
這種捕捉方式要求和被捕捉Controller在同一個(gè)類中造锅,一般實(shí)現(xiàn)方式是把ExceptionHandler放在BaseController中繼承撼唾,自由性差。
4.@ControllerAdvice注解
這種需要配合@ExceptionHandler屬于第三種方式變種哥蔚,我使用的則是這種方式的變種倒谷,下面詳解蛛蒙。

原因和源碼分析

首先來(lái)分析異常的處理過(guò)程

上面是簡(jiǎn)化的請(qǐng)求流轉(zhuǎn)圖,感謝某位同學(xué)的圖片渤愁。

下面是程序員趙鑫的原理分析圖牵祟,我搬來(lái)一用。當(dāng)然我沒(méi)有授權(quán)抖格,如果有爭(zhēng)議诺苹,我援引互聯(lián)網(wǎng)信息共享?xiàng)l例自護(hù)(黑人問(wèn)號(hào)臉)

照例感謝程序員趙鑫同學(xué),想看再詳細(xì)的分析請(qǐng)轉(zhuǎn)貼這里

上圖是SpringMVC的異常處理器結(jié)構(gòu)雹拄,HandlerExceptionResolver是一個(gè)接口收奔,留給自定義的時(shí)候使用。

異常處理器的處理順序是:異常->HandlerExceptionResolver->自定義異常->默認(rèn)異常處理器

SpringMVC的異常處理器通過(guò)實(shí)現(xiàn)Orderd接口滓玖,定義Order數(shù)值為每個(gè)異常處理器排序坪哄,圖中可以看到默認(rèn)異常處理器都繼承于AbstractHandlerExceptionResolver∈拼郏看圖:

默認(rèn)異常處理器都實(shí)現(xiàn)了doResolverException()方法翩肌。

圖上的每一個(gè)處理器其實(shí)都代表了可以實(shí)現(xiàn)全局自定義處理的一種或者多種方式。

404異常捕捉不能實(shí)現(xiàn)分析

在我實(shí)現(xiàn)全局處理異常的時(shí)候殊霞,發(fā)現(xiàn)404異常并沒(méi)有被捕捉摧阅,參考多方資料后發(fā)現(xiàn),SpringleMVC的機(jī)制默認(rèn)是對(duì)404異常不進(jìn)行拋出動(dòng)作的绷蹲,直接在Response中設(shè)置錯(cuò)誤代碼,直接返回顾孽。具體看圖:

圖中的屬性 throwExceptionIfNoHandlerFound = false 就是404異常拋出錯(cuò)誤與否的判斷屬性,下面是判斷源碼祝钢,throwExceptionIfNoHandlerFound為true則會(huì)拋出異常

原因和源碼分析

下面對(duì)幾種實(shí)現(xiàn)方式進(jìn)行解析

  • 1.SimpleMappingExceptionResolver
    代碼如下:這里不做詳解,缺點(diǎn)是不靈活若厚,獲取信息參數(shù)有限
  <!-- 出現(xiàn)異常會(huì)跳到這個(gè)頁(yè)面拦英,總錯(cuò)誤處理-->
 <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="defaultErrorView">
           <value>/error/error</value>
      </property>
      <property name="defaultStatusCode">
           <value>500</value>
      </property>
      <property name="warnLogCategory">
           <value>org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver</value>
      </property>
  <!-- 如果不設(shè)置exceptionMappings,就會(huì)全局異常捕獲 -->
      <property name="exceptionMappings">
           <props>
                <prop key="Java.sql.SQLException">/error/error</prop>
                <prop key="Java.lang.RuntimeException">/error/error</prop>
                <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">/error/error</prop>
           </props>
      </property>
</bean>

  • 2.HandlerExceptionResolver
    這種實(shí)現(xiàn)方式會(huì)受到容器加載順序影響(可能是這個(gè)原因),如果沒(méi)有加載完全测秸,會(huì)按照SpringMVC默認(rèn)的配置文件中的處理順序進(jìn)行處理
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
          Map<String, Object> model = new HashMap<String, Object>();
          model.put("ex", ex);
  
          // 根據(jù)不同錯(cuò)誤轉(zhuǎn)向不同頁(yè)面
          if(ex instanceof BusinessException) {
               return new ModelAndView("error-business", model);
          }else if(ex instanceof ParameterException) {
               return new ModelAndView("error-parameter", model);
          } else {
               return new ModelAndView("error", model);
          }
     }
}

這種需要在Spring的配置文件applicationContext.xml中增加以下內(nèi)容:

//有一種說(shuō)法疤估,如果不起作用,id寫(xiě)成這樣說(shuō)不定能解決問(wèn)題霎冯,因?yàn)榧虞d的時(shí)候是按照這個(gè)id去加載的
< bean id="handlerExceptionResolver" class="cn.basttg.core.exception.MyExceptionHandler"/>  
  • 3.ControllerAdvice(和ExceptionHandler放在一起了)

@ControllerAdvice  
public class ExceptionHandler {  
  
    @ResponseBody  
    @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class//這里可以選擇其他具體的異常)  
    public ResponseModel handleUnexpectedServerError(Exception ex) {  
        ex.printStackTrace();  
  
        // 處理異常  
        ResponseModel response = new ResponseModel();  
  
        String errorMsg = ex.getMessage();  
        response.setCode(Integer.valueOf(ResultCode.SYSTEM_EXCEPTION));  
        if(errorMsg.contains("excpStart")){  
            errorMsg = errorMsg.substring(errorMsg.indexOf("excpStart") + 9, errorMsg.indexOf("excpEnd"));  
        }  
        response.setMessage(errorMsg);  
  
        // 返回?cái)?shù)據(jù)  
        return response;  
    }  
  • 4.ControllerAdvice(我的實(shí)現(xiàn)方式)
@ControllerAdvice
public class GlobalExceptionResolver extends DefaultHandlerExceptionResolver {

    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        String url = request.getServletPath();
        if (url.startsWith("/api")) {//api返回異常攔截

            if (ex instanceof HttpRequestMethodNotSupportedException) {
                setResponseParam(response, 405, "請(qǐng)求方式錯(cuò)誤铃拇!");
                return null;
            }

            if (ex instanceof MissingServletRequestParameterException) {
                setResponseParam(response, 400, "錯(cuò)誤請(qǐng)求!");
                return null;
            }

            if (ex instanceof NoHandlerFoundException) {
                //可以進(jìn)行其他方法處理沈撞,LOG或者什么詳細(xì)記錄慷荔,我這里直接返回JSON
                setResponseParam(response, 404, "請(qǐng)求路徑錯(cuò)誤!");
                return null;
            }

            setResponseParam(response, 500, "服務(wù)器內(nèi)部錯(cuò)誤缠俺!服務(wù)暫時(shí)不可用显晶!");
            return null;
        }
        
        //這里調(diào)用父類的異常處理方法贷岸,實(shí)現(xiàn)其他不需要的異常交給SpringMVC處理
        return super.doResolveException(request, response, handler, ex);
        
    }

    private void setResponseParam(HttpServletResponse response, int code, String msg) throws IOException {
        JSONObject j = JSONObject.fromObject(R.error(code, msg));
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(j.toString());
    }
}

參考:

公子的專欄焰尾迭磷雇、程序猿之洞偿警、Exception Handling in Spring MVC程序員趙鑫唯笙、

最終總結(jié)

聯(lián)系點(diǎn)這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螟蒸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子睁本,更是在濱河造成了極大的恐慌尿庐,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呢堰,死亡現(xiàn)場(chǎng)離奇詭異抄瑟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)枉疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)皮假,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人骂维,你說(shuō)我怎么就攤上這事惹资。” “怎么了航闺?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵褪测,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我潦刃,道長(zhǎng)侮措,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任乖杠,我火速辦了婚禮分扎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胧洒。我一直安慰自己畏吓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布卫漫。 她就那樣靜靜地躺著菲饼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汛兜。 梳的紋絲不亂的頭發(fā)上巴粪,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼肛根。 笑死辫塌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的派哲。 我是一名探鬼主播臼氨,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芭届!你這毒婦竟也來(lái)了储矩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤褂乍,失蹤者是張志新(化名)和其女友劉穎持隧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逃片,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屡拨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褥实。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呀狼。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖损离,靈堂內(nèi)的尸體忽然破棺而出哥艇,到底是詐尸還是另有隱情,我是刑警寧澤僻澎,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布貌踏,位于F島的核電站,受9級(jí)特大地震影響窟勃,放射性物質(zhì)發(fā)生泄漏哩俭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一拳恋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砸捏,春花似錦谬运、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至掂骏,卻和暖如春轰驳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工级解, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冒黑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓勤哗,卻偏偏與公主長(zhǎng)得像抡爹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芒划,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348