在實際項目中,如果出現了異常,我們不希望直接把異常拋給用戶,應該對異常進行處理,然後返回一個友好的信息給用戶。這節主要總結一下項目中如何使用SpringBoot如何攔截全局的異常。
1. 定義返回的json結構
請求接口需要返回json數據,一般後臺會統一定義一個返回給前端的數據結構,包括code、msg信息等,這可以參考【1】SpringBoot返回Json數據及封裝中封裝的統一json結構,如下:
public class JsonResult {
/**
* 異常碼
*/
protected String code;
/**
* 異常信息
*/
protected String msg;
protected JsonResult() {}
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
// get set
}
2. 處理系統異常
新建一個GlobalExceptionHandler全局異常處理類,然後加上@ControllerAdvice註解即可攔截項目中拋出的異常,該註解中也可以跟上basePackages屬性,用來指定攔截哪個包中的異常,一般我們可以不指定,所有異常都攔截。
@ControllerAdvice
public class GlobalExceptionHandler {
}
下面舉幾個例子來說明一下如何使用。
2.1 處理不合法的請求格式異常
有些時候,請求格式不合法,會拋出TypeMismatchException,我們可以攔截該異常,做一個友好處理:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 不合法的請求格式異常
* @param ex
* @return
*/
@ExceptionHandler(TypeMismatchException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public JsonResult handleTypeMismatchException(TypeMismatchException ex) {
logger.error("不合法的請求格式", ex);
return new JsonResult("400", "不合法的請求格式");
}
}
2.2 HTTP參數不可讀異常
有些時候,請求參數不合法,會拋出HttpMessageNotReadableException,我們可以攔截該異常,做一個友好處理:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* HTTP參數不可讀
* @param ex
* @return
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public JsonResult handleHttpMessageNotReadableException(
HttpMessageNotReadableException ex) {
logger.error("請求參數不可讀", ex);
return new JsonResult("400", "請求參數不可讀");
}
}
2.3 一勞永逸?
當然了,異常很多,比如還有RuntimeException,數據庫還有一些查詢或者操作異常等等。由於Exception異常是父類,所有異常都會繼承該異常,所以我們可以直接攔截Exception異常:
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 系統異常 預期以外異常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public JsonResult handleUnexpectedServer(Exception ex) {
logger.error("系統異常:", ex);
return new JsonResult("500", "系統發生異常,請聯繫管理員");
}
}
但是項目中,我們一般都會比較詳細的去攔截一些常見異常,攔截Exception雖然可以一勞永逸,但是不利於我們去排查或者定位問題。實際項目中,可以把攔截Exception異常寫在GlobalExceptionHandler最下面,如果都沒有找到,最後再攔截一下Exception異常,保證輸出信息友好。
3. 攔截自定義異常
在實際項目中,除了攔截一些系統異常外,在某些業務上,我們需要自定義一些異常,比如在微服務中,服務之間的相互調用很平凡,很常見。要處理一個服務的調用,那麼可能會調用失敗,此時我們需要自定義一個異常,當調用失敗時拋出來,給GlobalExceptionHandler去捕獲。
3.1 定義異常信息
由於在業務中,有很多異常,針對不同的業務,可能給出的提示信息不同,所以為了方便項目管理,我們一般會定義一個異常信息枚舉類。
/**
* 業務異常提示信息枚舉類
* @author shengwu ni
*/
public enum BusinessMsgEnum {
/** 參數異常 */
PARMETER_EXCEPTION("102", "參數異常!"),
/** 等待超時 */
SERVICE_TIME_OUT("103", "服務調用超時!"),
/** 參數過大 */
PARMETER_BIG_EXCEPTION("102", "輸入的圖片數量不能超過50張!"),
/** 500 : 一勞永逸的提示也可以在這定義 */
UNEXPECTED_EXCEPTION("500", "系統發生異常,請聯繫管理員!");
// 還可以定義更多的業務異常
/**
* 消息碼
*/
private String code;
/**
* 消息內容
*/
private String msg;
private BusinessMsgEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
// set get方法
}
3.2 攔截自定義異常
然後我們定義一個業務異常,當出現業務異常時,我們就拋這個自定義的業務異常即可。
/**
* 自定義業務異常
* @author shengwu ni
*/
public class BusinessErrorException extends RuntimeException {
private static final long serialVersionUID = -7480022450501760611L;
/**
* 異常碼
*/
private String code;
/**
* 異常提示信息
*/
private String message;
public BusinessErrorException(BusinessMsgEnum businessMsgEnum) {
this.code = businessMsgEnum.code();
this.message = businessMsgEnum.msg();
}
// get set方法
}
構造方法中,傳入我們上面自定義的異常枚舉類,所以在項目中,如果有信息異常信息,我們直接在枚舉類中添加即可,然後再攔截該異常時獲取即可。
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 攔截業務異常,返回業務異常信息
* @param ex
* @return
*/
@ExceptionHandler(BusinessErrorException.class)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public JsonResult handleBusinessError(BusinessErrorException ex) {
String code = ex.getCode();
String message = ex.getMessage();
return new JsonResult(code, message);
}
}
在業務代碼中,我們可以直接在拋出業務異常,測試一下:
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/exception")
public String testException() {
try {
int i = 1 / 0;
} catch (Exception e) {
throw new BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
}
return null;
}
}
運行一下項目,測試一下,返回json如下;
{"code":"500","msg":"系統發生異常,請聯繫管理員!"}
SpringBoot的全局異常攔截處理就總結這麼多,在項目中運用也很廣泛,基本上每個項目中都需要全局異常處理。
可獲得兩大新人禮包
36份一線互聯網Java面試電子書
84個Java稀缺面試題視頻
閱讀更多 李紅 的文章