1. 程式人生 > 實用技巧 >springboot全域性異常處理

springboot全域性異常處理

專案中遇到執行時異常,總不能每個異常處理都去新增try catch邏輯,甚至有的時候一個偶然條件發生而導致異常,而我們沒有進行對應的處理則會直接給請求者返回未知的錯誤,這在正式的上線的專案中是不允許,所以我們來配置全域性異常處理

今天講解一下如何在SpringBoot實現全域性異常機制,在沒有用springboot大家要實現這一的功能基本上都是通過aop的思想,還是有點麻煩,而現在springboot中對它要進行了一次封裝,開發者使用起來更加的簡單,接下先通過程式碼演示效果,然後再分析一下原理,好了廢話不多說直接上程式碼,看程式碼結構:

1、使用到的註解:@ControllerAdvice註解是用來配置控制器通知的,我們可以配置過濾攔截具體一種或者多種型別的註解,新增annotations屬性即可,在類的上方我們配置了@ControllerAdvice的annotations屬性值為RestController.class,也就是隻有添加了@RestController註解的控制器才會進入全域性異常處理;因為我們全域性返回的都是統一的Json格式的字串,所以需要再類上配置@ResponseBody註解;@ExceptionHandler註解用來配置需要攔截的異常型別,預設是全域性型別,可以通過value屬性配置只對某個型別的異常起作用;@ResponseStatus註解用於配置遇到該異常後返回資料時的StatusCode的值,我們這裡預設使用值500

定義一個返回的DTO工具類

//
// 定義一個返回的DTO工具類
//

package com.zkml.common.obj.dto;

import com.zkml.common.obj.enums.ResultStatusEnum;
import java.io.Serializable;

public class ResultModelDTO<T> implements Serializable {
    private ResultStatusEnum result;
    private Long code;
    private String message;
    private T model;

    public ResultModelDTO() {
    }

    public boolean successResult() {
        return this.result.equals(ResultStatusEnum.success);
    }

    public boolean failResult() {
        return !this.successResult();
    }

    public ResultModelDTO(ResultStatusEnum result, long code, String message, T model) {
        this.result = result;
        this.code = Long.valueOf(code);
        this.message = message;
        this.model = model;
    }

    public ResultModelDTO(ResultStatusEnum result, ResultCode remoteResultCode, T model) {
        this(result, remoteResultCode.getCode(), remoteResultCode.getMessage(), model);
    }

    public ResultStatusEnum getResult() {
        return this.result;
    }

    public void setResult(ResultStatusEnum result) {
        this.result = result;
    }

    public Long getCode() {
        return this.code;
    }

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

    public String getMessage() {
        return this.message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getModel() {
        return this.model;
    }

    public void setModel(T model) {
        this.model = model;
    }
}

定義一個全域性異常處理類

package com.springboot.exception.global_excep;
 
import com.springboot.exception.json.JsonResult;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import lombok.extern.slf4j.Slf4j 
import java.io.IOException;
 
/**
 * @Author fanghui
 * @Description 全域性異常攔截器
 * @Date 16:38 2019/8/14
 * @Modify By
 */
@ControllerAdvice
@ResponseBody
@Slf4j 
public class GlobalExceptionHandler {
 
 private String getMessage(BindingResult result) {
        return result.getFieldErrors().stream().map(error -> {
            return error.getField() + "[" + error.getRejectedValue() + "]:" + error.getDefaultMessage();
        }).collect(Collectors.joining(", "));
    }

    /**
     * 捕獲 controller 實體類引數@Validate驗證報錯
     *
     * @param e
     * @param request
     * @param response
     * @return
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public JsonResult validExceptionHandler(BindException e, HttpServletRequest request, HttpServletResponse response) {
        return JsonResult.error(getMessage(e.getBindingResult()), ErrorCode.ILLEGAL_PARAM.name());
    }

    /**
     * 捕獲 普通方法傳參@Validate驗證報錯
     *
     * @param e
     * @param request
     * @param response
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public JsonResult validExceptionHandler(MethodArgumentNotValidException e, HttpServletRequest request, HttpServletResponse response) {
        return JsonResult.error(getMessage(e.getBindingResult()), ErrorCode.ILLEGAL_PARAM.name());
    }

    /**
     * 捕獲 controller 平鋪引數@Validate驗證報錯
     *
     * @param e
     * @param request
     * @param response
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public JsonResult validExceptionHandler(ConstraintViolationException e, HttpServletRequest request, HttpServletResponse response) {
        return JsonResult.error(e.getMessage(), ErrorCode.ILLEGAL_PARAM.name());
    }

    //執行時異常
    @ExceptionHandler(RuntimeException.class)
    public String runtimeExceptionHandler(HttpServletRequest request,RuntimeException ex) {
         log.info("請求路徑:"+request.getRequestURL().toString()+"+"RuntimeException :"+ex);
        return ResultModelUtil.failResult(DefaultResultCodeEnum.ADD_ERROR);
    }
 
    //空指標異常
    @ExceptionHandler(NullPointerException.class)
    public String nullPointerExceptionHandler(HttpServletRequest request,NullPointerException ex) {
          log.info("請求路徑:"+request.getRequestURL().toString()+"+"NullPointerException :"+ex);
        return ResultModelUtil.failResult(DefaultResultCodeEnum.ADD_ERROR);
    }
 
    //型別轉換異常
    @ExceptionHandler(ClassCastException.class)
    public String classCastExceptionHandler(HttpServletRequest request,ClassCastException ex) {
         log.info("請求路徑:"+request.getRequestURL().toString()+"+"ClassCastException :"+ex);
        return resultFormat(3, ex);
    }
 
    //IO異常
    @ExceptionHandler(IOException.class)
    public String iOExceptionHandler(HttpServletRequest request,IOException ex) {
        log.info("請求路徑:"+request.getRequestURL().toString()+"+"IOException :"+ex);
        return resultFormat(4, ex);
    }
 
    //未知方法異常
    @ExceptionHandler(NoSuchMethodException.class)
    public String noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        return resultFormat(5, ex);
    }
 
    //陣列越界異常
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public String indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        return resultFormat(6, ex);
    }
 
    //400錯誤
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public String requestNotReadable(HttpMessageNotReadableException ex) {
        System.out.println("400..requestNotReadable");
        return resultFormat(7, ex);
    }
 
    //400錯誤
    @ExceptionHandler({TypeMismatchException.class})
    public String requestTypeMismatch(TypeMismatchException ex) {
        System.out.println("400..TypeMismatchException");
        return resultFormat(8, ex);
    }
 
    //400錯誤
    @ExceptionHandler({MissingServletRequestParameterException.class})
    public String requestMissingServletRequest(MissingServletRequestParameterException ex) {
        System.out.println("400..MissingServletRequest");
        return resultFormat(9, ex);
    }
 
    //405錯誤
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public String request405(HttpRequestMethodNotSupportedException ex) {
        return resultFormat(10, ex);
    }
 
    //406錯誤
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public String request406(HttpMediaTypeNotAcceptableException ex) {
        System.out.println("406...");
        return resultFormat(11, ex);
    }
 
    //500錯誤
    @ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
    public String server500(RuntimeException ex) {
        System.out.println("500...");
        return resultFormat(12, ex);
    }
 
    //棧溢位
    @ExceptionHandler({StackOverflowError.class})
    public String requestStackOverflow(StackOverflowError ex) {
        return resultFormat(13, ex);
    }
 
    //除數不能為0
    @ExceptionHandler({ArithmeticException.class})
    public String arithmeticException(ArithmeticException ex) {
        return resultFormat(13, ex);
    }
 
 
    //其他錯誤
    @ExceptionHandler({Exception.class})
    public String exception(Exception ex) {
        return resultFormat(14, ex);
    }
 
    private <T extends Throwable> String resultFormat(Integer code, T ex) {
        ex.printStackTrace();
        log.error(String.format(logExceptionFormat, code, ex.getMessage()));
        return JsonResult.failed(code, ex.getMessage());
    }
 
}

除了上面的執行時異常之外,還有一個是專門來處理業務異常的,接下來我們再來處理一個自定義業務異常類:

自定義業務異常類BaseException

package com.izkml.mlyun.framework.common.exception;

public class BaseException extends RuntimeException {
    private String errorCode;

    public String getErrorCode() {
        return this.errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public BaseException(String errorCode, String message) {
        this(errorCode, message, (Throwable)null);
    }

    public BaseException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public BaseException(ErrorEnum errorEnum) {
        this(errorEnum.getCode(), errorEnum.getText(), (Throwable)null);
    }

    public BaseException(ErrorEnum errorEnum, Throwable cause) {
        this(errorEnum.getCode(), errorEnum.getText(), cause);
    }
}

SpringMVC@ResponseStatus註解的使用

有時候,我們在實戰專案中並不關心返回訊息體的內容,前端只關係後端返回的狀態碼到底是什麼,這個時候我們就要做的專業點,那麼此時這個註解就派上用場了

    //405-Method Not Allowed
    @ExceptionHandler(HTTPRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)  //這個HttpStatus是個列舉類,可以點選進去有很多不同列舉
    public Map<String,Object> server500(RuntimeException ex) {
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",400);
        map.put("message",e.getMessage());
        return map;
    }

總結

我們開發無論什麼服務都大體分為三層,controller,service,dao;其中servic一般不做任何系統型別異常處理(RuntimeException和Exception),只關注業務分析,但是有一種情況需要往上進行拋異常(比如,業務層中車牌號重複,需要告訴上游你的車牌號已經重複,需要一層一層的往上進行拋直到前端頁面告知使用者);除了這種業務需要判斷的異常往上拋之外,異常需要在controller進行捕捉,通過try..catch..進行捕獲或者使用全域性異常進行攔截,事實上,對於伺服器來說,它是200,正常返回的,而對不業務模組來說,它的狀態是個500,嘿嘿,這點要清楚