Spring Boot 全局異常處理

完善的異常處理可以讓客戶端有一個(gè)良好的體驗(yàn),并且有利于定位出錯(cuò)原因,幫助解決問(wèn)題。
Spring Boot 內(nèi)置了一個(gè) /error 處理,當(dāng)拋出異常之后,會(huì)被轉(zhuǎn)到這個(gè)映射進(jìn)行處理, 就是常見的 Whitelable Error Page 。

image.png

但是這個(gè)界面長(zhǎng)相難看,不夠友好,需要我們做一些工作。

一、傳統(tǒng)項(xiàng)目中的錯(cuò)誤頁(yè)面

項(xiàng)目中發(fā)生異常之后,最好能給予一些必要的提示,在開發(fā)階段甚至可以打印出完整的堆棧信息,以快速定位解決問(wèn)題。

1)創(chuàng)建一個(gè)錯(cuò)誤界面error.html,用于展示錯(cuò)誤內(nèi)容

在resources>templates文件夾下,新建一個(gè)error.html的模板文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot 錯(cuò)誤處理</title>
</head>
<body>
    <h1>錯(cuò)誤處理</h1>
    <h3 th:text="'錯(cuò)誤信息:'+${msg}"></h3>
    <h3 th:text="'請(qǐng)求地址:'+${url}"></h3>
    <h2>異常堆棧跟蹤日志StackTrace</h2>
    <div th:each="line:${stackTrace}">
        <div th:text="${line}"></div>
    </div>
</body>
</html>

2)創(chuàng)建一個(gè)全局處理類。使用 @ControllerAdvice@ExceptionHandler注解來(lái)處理錯(cuò)誤信息

新建一個(gè)exception的包,然后在此包下新建一個(gè)PageExceptionHandler的類

/**
 * 傳統(tǒng)系統(tǒng)中的有錯(cuò)誤界面
 *
 * @author xiaozhao
 * @date 2018/10/23下午1:59
 */
@ControllerAdvice
public class PageExceptionHandler {


    /**
     * 返回到錯(cuò)誤頁(yè)面,位于resources>templates>error.html文件
     *
     * @param req
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = Exception.class)
    public ModelAndView pageExceptionHandle(HttpServletRequest req, Exception e) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", e.getMessage());
        modelAndView.addObject("url", req.getRequestURL());
        // 模板名稱
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

現(xiàn)在使用一個(gè)控制器來(lái)測(cè)試一下,在控制器的代碼中拋出異常,例如:

/**
     * 傳統(tǒng)項(xiàng)目中的界面
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/page")
    public String page() throws Exception {
        throw new Exception("發(fā)生了界面錯(cuò)誤");
    }

運(yùn)行之后,發(fā)出存在錯(cuò)誤的請(qǐng)求,顯示如下的界面

image.png

二、rest類型的錯(cuò)誤展示

這種場(chǎng)景下是不需要error.html的,只需要修改下@ControllerAdvice修飾的類即可,修改方法的返回值,然后添加@ResponseBody的注解

或者直接把@ControllerAdvice修改為@RestControllerAdvice即可,下面的方法中不再需要@ResponseBody注解

@RestControllerAdvice
public class RestExceptionHandler {
  @ExceptionHandler(value = Exception.class)
    public HttpResult<String> restExceptionHandle(Exception e) {
        //......
    }
}

或者

@ControllerAdvice
public class RestExceptionHandler {
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public HttpResult<String> restExceptionHandle(Exception e) {
        //......
    }
}

這里使用第一種方式

/**
 * 處理類似rest風(fēng)格的接口,返回json
 *
 * @author xiaozhao
 * @date 2018/10/23下午1:32
 */
@RestControllerAdvice
public class RestExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);

    /**
     * 返回json
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public HttpResult<String> restExceptionHandle(Exception e) {
        logger.error(e.getMessage());
        HttpResult<String> result = new HttpResult<>();
        result.setCode(-1);
        result.setMsg(e.getMessage());
        return result;
    }
}

其中的HttpResult是一個(gè)包裝類,統(tǒng)一返回格式

/**
 * http請(qǐng)求返回的最外層對(duì)象
 *
 * @author xiaozhao
 * @date 2018/10/23下午1:34
 */
public class HttpResult<T> {
    /**
     * 錯(cuò)誤碼
     */
    private Integer code;

    /**
     * 提示信息
     */
    private String msg;

    /**
     * 具體的內(nèi)容
     */
    private T data;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "HttpResult{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

控制器調(diào)用代碼

/**
     * rest服務(wù),返回json數(shù)據(jù)
     *
     * @return
     * @throws RestException
     */
    @GetMapping("/rest")
    @ResponseBody
    public String hello() throws RestException {
        throw new RestException("發(fā)生自定義rest錯(cuò)誤");
    }

運(yùn)行后的結(jié)果


image.png

三、統(tǒng)一2種顯示

一般項(xiàng)目中可能會(huì)同時(shí)存在2種形式,上面2種形式差別就是返回值的不同,可以統(tǒng)一起來(lái),都返回Object類型,然后內(nèi)部根據(jù)請(qǐng)求頭的信息,判斷是那種類型的請(qǐng)求。

當(dāng)判斷請(qǐng)求為jons格式時(shí),使用HttpResult進(jìn)行返回,當(dāng)請(qǐng)求為界面時(shí),返回ModelAndView。

完整代碼如下:

**
 * 處理項(xiàng)目中的異常,既包含傳統(tǒng)項(xiàng)目中的界面,也包含了rest形式的json返回
 *
 * @author xiaozhao
 * @date 2018/10/23下午3:12
 */
@RestControllerAdvice
public class GlobalExceptionHandler2 {
    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler2.class);

    @ExceptionHandler(Exception.class)
    public Object defaultExceptionHandle(Exception e, HttpServletRequest request) {
        boolean isAjax = isAjax(request);
        if (isAjax) {
            logger.error("Ajax 請(qǐng)求異常" + e.getMessage());
            HttpResult<String> result = new HttpResult<>();
            result.setCode(-1);
            result.setMsg(e.getMessage());
            return result;
        } else {
            logger.error("界面請(qǐng)求異常" + e.getMessage());
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("msg", e.getMessage());
            modelAndView.addObject("url", request.getRequestURL());
            modelAndView.addObject("stackTrace", e.getStackTrace());
            modelAndView.setViewName("error");
            return modelAndView;
        }
    }

    /**
     * 判斷網(wǎng)絡(luò)請(qǐng)求是否為ajax
     *
     * @param req
     * @return
     */
    private boolean isAjax(HttpServletRequest req) {
        String contentTypeHeader = req.getHeader("Content-Type");
        String acceptHeader = req.getHeader("Accept");
        String xRequestedWith = req.getHeader("X-Requested-With");
        return (contentTypeHeader != null && contentTypeHeader.contains("application/json"))
                || (acceptHeader != null && acceptHeader.contains("application/json"))
                || "XMLHttpRequest".equalsIgnoreCase(xRequestedWith);
    }
}

最終代碼

https://github.com/xiaozhaowen/spring-boot-in-action/tree/master/springboot-handle-exception

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容