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,嘿嘿,這點要清楚