1. 程式人生 > >Spring 框架——利用HandlerExceptionResolver實現全域性異常捕獲

Spring 框架——利用HandlerExceptionResolver實現全域性異常捕獲

一、需求描述

        因為在專案中,我們不可否認的會出現異常,而且這些異常並沒有進行捕獲。經常出現的bug如空指標異常等等。在之前的專案中,如果我們沒有進行任何配置,那麼容器會自動列印錯誤的資訊,如果tomcat的404頁面,400頁面等等。如果我們在web.xml中進行如下配置,就會攔截錯誤,然後跳轉到指定的錯誤頁面。

<error-page>
    <error-code>500</error-code>
    <location>/500.jsp</location>
</error-page>

但是這已經落後了,現在我們通過實現spring的HandlerExceptionResolver介面來捕獲所有的異常。

二、基本應用

        首先我們新建GlobalExceptionResolver如下:

public class GlobalExceptionResolver implements HandlerExceptionResolver{
​
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception exception) {
        //--------------------------------------------
        return null;
    }
}

然後在spring配置檔案中配置剛才新建的類,或者加上@Component註解。

<!--全域性異常捕捉 -->
<bean class="com.ssm.exception.GlobalExceptionResolver" />

現在就可以根據自己的需求修改GlobalExceptionResolver的橫線部分了,在你在開發的時候,請返回null,這樣這個類就如同不起作用,之前該怎麼樣就怎麼樣。當開發完成之後,根據錯誤的資訊來返回需要的東西了。首先我們可以做的是列印錯誤日誌,當然也是必須的。

System.err.println("訪問" + request.getRequestURI() + " 發生錯誤, 錯誤資訊:" + exception.getMessage());

這裡我只是用控制檯舉例,你當然可以用日誌框架列印。列印資訊主要是包括是訪問那個地址出現了什麼錯誤。之後如果你需要返回錯誤頁面,那麼就直接在ModelAndView裡面寫就行了,這裡就不多舉例了,ModelAndView寫過MVC的Controller應該都熟悉。

ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error");
return modelAndView;

 以上其實就已經實現了全域性捕獲異常的功能,你可以自己丟擲一個不捕獲的異常測試一下是否成功。

三、其他說明

注意不同型別和來源的請求

        因為在實際專案中,可能遇到各種請求型別,如正常的get,post。也可能是來自ajax的請求。所以如果均返回同一個ModelAndView顯然可能有點不合適,對於ajax可能需要特別處理。

        還有就是如果有手機端和PC在同一個專案中的情況,那麼來源不同,返回的頁面也可能不同。雖然可以交給前端去做自適應處理,但是我們還是得做好考慮。

        總之,要考慮到各種不同的請求,單一返回可能並不適用所有專案。

GlobalExceptionResolver這個類推薦放在exception包下,屬於一種自定義異常

        這個配置推薦放在和web相關的spring配置下,因為和類似一個controller

spring是怎麼做到的?handler引數又是什麼?

        首先spring官方文件536頁說明了HandlerExceptionResolve,而官方推薦的是使用@ExceptionHandler註解去捕獲固定的異常。然後我查了原始碼,spring原始碼中:

/**
     * Resolve the exception by iterating over the list of configured exception resolvers.
     * The first one to return a ModelAndView instance wins. Otherwise {@code null} is returned.
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {
        if (resolvers != null) {
            for (HandlerExceptionResolver handlerExceptionResolver : resolvers) {
                ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
                if (mav != null) {
                    return mav;
                }
            }
        }
        return null;
    }

這是spring預設實現,也就是說,我們沒有重寫的話,spring是這樣執行的,從命名來瞎說就是。如果出現異常(private List<HandlerExceptionResolver> resolvers;),處理異常解析器就會非空,通過迴圈異常解析器處理resolvers中的異常,然後處理。最後返回null也就是我們之前所說的不做任何錯誤頁面的那種處理。然後處理異常列印異常資訊是在抽象類裡面完成的:

/**
     * Check whether this resolver is supposed to apply (i.e. if the supplied handler
     * matches any of the configured {@linkplain #setMappedHandlers handlers} or
     * {@linkplain #setMappedHandlerClasses handler classes}), and then delegate
     * to the {@link #doResolveException} template method.
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
            Object handler, Exception ex) {
​
        if (shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
            }
            prepareResponse(ex, response);
            ModelAndView result = doResolveException(request, response, handler, ex);
            if (result != null) {
                logException(ex, request);
            }
            return result;
        }
        else {
            return null;
        }
    }

就是列印錯誤資訊,這裡我們看到handler被列印了。列印的意思是從哪一個handler解析出什麼異常。最後如果有結果依舊返回。總之我們可以知道的是,spring的handle在處理時發現異常後,HandlerExceptionResolver的列表會被賦值,然後進行處理。

四、專案運用

        這裡我們可以參考《異常處理》一節閱讀,首先我們的全域性異常攔截器實現如下:

/**
 * 
 * 類名稱 : SgccExceptionResolver. <br>
 * 功能描述 : 全域性異常攔截器,可在此做異常資訊的判斷及輸出. <br>
 * <p>
 * 建立人: Administrator <br>
 * 建立時間 : 2017年7月3日 下午15:12:36. <br>
 * </p>
 * 修改歷史:  <br>
 * 修改人  修改日期   修改描述<br>
 * -----------------------------------<br>
 */
public class SgccExceptionResolver implements HandlerExceptionResolver {
​
    private Logger logger = Logger.getLogger(this.getClass());
​
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) {
        logger.info("==============Exception Start 000000=============");
        if (ex instanceof BaseException) {
            logger.debug(ex, ex);
        }else {
            logger.error(ex, ex);
        }
        logger.info("==============Exception End 000000=============");
        if (NetworkUtil.isAjax(request)) {
            String msg = null;
            String code = null;
            String detail = null;
            if (ex instanceof BaseException) {
                msg = ((BaseException) ex).getErrorMsg();
                code = ((BaseException) ex).getErrorCode();
                detail = ((BaseException) ex).getMsgDetail();
            }else {
                FSTErrorCode fc = FSTErrorCode.SYS_ERROR_000000;
                msg = fc.getErrorMsg();
                code = fc.getErrorCode();
                detail = fc.getMsgDetail();
            }
            try {
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("msg", msg);
                map.put("code", code);
                map.put("detail", detail);
                JSONObject json = JSONObject.fromObject(map);
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().print(json);
            }catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }else {
            ModelAndView mv = new ModelAndView();
            mv.setViewName("error/error");
            mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
            return mv;
        }
    }    
    
}