1. 程式人生 > >SpringMVC實現全域性異常處理器

SpringMVC實現全域性異常處理器

通過 @ControllerAdvice 註解,我們可以在一個地方對所有 @Controller 註解的控制器進行管理。
註解了 @ControllerAdvice 的類的方法可以使用 @ExceptionHandler@InitBinder@ModelAttribute 註解到方法上,這對所有註解了 @RequestMapping 的控制器內的方法都有效。

  1. @ExceptionHandler:用於捕獲所有控制器裡面的異常,並進行處理。
  2. @InitBinder:用來設定 WebDataBinderWebDataBinder 用來自動繫結前臺請求引數到 Model 中。
  3. @ModelAttribute
    @ModelAttribute 本來的作用是繫結鍵值對到 Model 裡,此處是讓全域性的@RequestMapping 都能獲得在此處設定的鍵值對。

本文使用 @ControllerAdvice + @ExceptionHandler 進行全域性的 Controller 層異常處理。只要設計得當,就再也不用在 Controller 層進行 try-catch 了!

一、經典案例

需求:希望通過全域性統一的異常處理將自定義錯誤碼以json的形式傳送給前端。

1、統一返回結果類 ApiResult

首先,定義一個統一結果返回類,最終需要將這個結果類的內容返回給前端。

package com.tao.smp.exception;

/**
 * Api統一的返回結果類
 */
public class ApiResult {
    /**
     * 結果碼
     */
    private String code;

    /**
     * 結果碼描述
     */
    private String msg;


    public ApiResult() {

    }

    public ApiResult(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }

    /**
     * 生成一個ApiResult物件, 並返回
     *
     * @param resultCode
     * @return
     */
    public static ApiResult of(ResultCode resultCode) {
        return new ApiResult(resultCode);
    }

    public String getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

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

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

2、錯誤碼列舉類 ResultCode

有了 ApiResult ,接下來需要定義一個列舉類, 來包含所有自定義的結果碼。

package com.tao.smp.exception;

/**
 * 錯誤碼
 */
public enum ResultCode {

    /**
     * 成功
     */
    SUCCESS("0", "success"),

    /**
     * 未知錯誤
     */
    UNKNOWN_ERROR("0x10001", "unkonwn error"),

    /**
     * 使用者名稱錯誤或不存在
     */
    USERNAME_ERROR("0x10002", "username error or does not exist"),

    /**
     * 密碼錯誤
     */
    PASSWORD_ERROR("0x10003", "password error"),

    /**
     * 使用者名稱不能為空
     */
    USERNAME_EMPTY("0x10004", "username can not be empty");

    /**
     * 結果碼
     */
    private String code;

    /**
     * 結果碼描述
     */
    private String msg;


    ResultCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

3、自定義業務異常類 BusinessRuntimeException

接下來需要定義我們自己的業務異常類,以後和業務相關的異常通通丟擲這個異常類,我們將錯誤碼列舉變數的值存於其中。

package com.tao.smp.exception;

/**
 * 自定義業務異常
 */
public class BusinessRuntimeException extends RuntimeException {

    /**
     * 結果碼
     */
    private String code;

    /**
     * 結果碼描述
     */
    private String msg;

    /**
     * 結果碼列舉
     */
    private ResultCode resultCode;


    public BusinessRuntimeException(ResultCode resultCode) {
        super(resultCode.getMsg());
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
        this.resultCode = resultCode;
    }

    public String getCode() {
        return code;
    }

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

    public String getMsg() {
        return msg;
    }

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

    public ResultCode getResultCode() {
        return resultCode;
    }

    public void setResultCode(ResultCode resultCode) {
        this.resultCode = resultCode;
    }
}

4、全域性異常處理類 GlobalExceptionResolver

最後便是定義全域性異常處理類。

  1. 通過 @ControllerAdvice 指定該類為 Controller 增強類。
  2. 通過 @ExceptionHandler 自定捕獲的異常型別。
  3. 通過 @ResponseBody 返回 json 到前端。

注意一點:被@ControllerAdvice註解的全域性異常處理類也是一個 Controller ,我們需要配置掃描路徑,確保能夠掃描到這個Controller。

package com.tao.smp.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 全域性Controller層異常處理類
 */
@ControllerAdvice
public class GlobalExceptionResolver {

    private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionResolver.class);

    /**
     * 處理所有不可知異常
     *
     * @param e 異常
     * @return json結果
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ApiResult handleException(Exception e) {
        // 列印異常堆疊資訊
        LOG.error(e.getMessage(), e);
        return ApiResult.of(ResultCode.UNKNOWN_ERROR);
    }

    /**
     * 處理所有業務異常
     *
     * @param e 業務異常
     * @return json結果
     */
    @ExceptionHandler(BusinessRuntimeException.class)
    @ResponseBody
    public ApiResult handleOpdRuntimeException(BusinessRuntimeException e) {
        // 不列印異常堆疊資訊
        LOG.error(e.getMsg());
        return ApiResult.of(e.getResultCode());
    }
}

二、測試

1、測試 TestController

package com.tao.smp.controller;

import com.tao.smp.exception.BusinessRuntimeException;
import com.tao.smp.exception.ResultCode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 測試異常的丟擲
 */
@Controller
@RequestMapping("/")
public class TestController {

    /**
     * 測試返回異常資訊
     * @return
     */
    @GetMapping("/exception")
    public String returnExceptionInfo() {

        if (1 != 2) {
            // 使用者民錯誤或不存在異常
            throw new BusinessRuntimeException(ResultCode.USERNAME_ERROR);
        }

        return "success";
    }
}

這裡寫圖片描述

這裡寫圖片描述