前言:在我们开发项目过程中,经常会主动的抛出异常提示,又或者程序出现了未知的异常而抛出的堆栈异常,此时,我们调用方会收到下面的信息(以swagger为例):
{
"timestamp": "2020-05-18T14:21:05.180+0000",
"status": 500,
"error": "Internal Server Error",
"message": "参数错误",
"path": "/err2"
}
这种方式很不优雅,那么有没有一种优雅点的方式,来统一处理程序抛出的异常,并友好的返回给调用方呢?
下面将介绍,如何在项目中封装异常处理,使之统一而优雅。
以RESTful项目为例:
一、在RESTful架构中,一般使用JSON来和调用方交互,我们先来定义一个返回数据的包装类,当我们正常返回数据或者遇到异常返回提示信息,都使用统一的格式返回给调用方
@Setter
@Getter
public class RespResult<T> {
private String code;
private String msg;
private T data;
public RespResult(String code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> RespResult<T> ok() {
return ok(null);
}
public static <T> RespResult<T> ok(T data) {
return new RespResult<>("000", "success", data);
}
public static <T> RespResult<T> error() {
return error("500", "unknown error", null);
}
public static <T> RespResult<T> error(String msg) {
return error("500", msg, null);
}
public static <T> RespResult<T> error(String code, String msg, T data) {
return new RespResult<>(code, msg, data);
}
}
二、自定义一个异常信息枚举、一个异常类,并封装相应的异常包装类,可以更优雅的创建异常
/***************自定义异常信息枚举*********************/
@Getter
public enum ErrorCodeEnum {
//自定义异常枚举
ILLEGAL_PARAM("001", "参数错误");
private String code;
private String msg;
ErrorCodeEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
/***************自定义异常类*********************/
@Getter
public class MyException extends RuntimeException {
private String code;
protected MyException(String code, String msg) {
super(msg);
this.code = code;
}
protected MyException(ErrorCodeEnum errorCodeEnum) {
super(errorCodeEnum.getMsg());
this.code = errorCodeEnum.getCode();
}
}
/***************包装一个异常创建类*********************/
public class MyExceptionFactory {
private MyExceptionFactory() {
}
public static MyException create(String code, String msg) {
return new MyException(code, msg);
}
public static MyException create(ErrorCodeEnum errorCodeEnum) {
return new MyException(errorCodeEnum.getCode(), errorCodeEnum.getMsg());
}
public static MyException create(ErrorCodeEnum errorCodeEnum, String msg) {
return new MyException(errorCodeEnum.getCode(), msg);
}
}
三、统一处理异常
想要统一处理异常,我们需要用到@RestControllerAdvice这个注解,这个注解有三个作用:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
我们这里用到的是第一种功能。
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(MyException.class)
public <T> RespResult<T> handleMyException(MyException e) {
return RespResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(IllegalArgumentException.class)
public <T> RespResult<T> handleMyException(IllegalArgumentException e) {
return RespResult.error(ErrorCodeEnum.ILLEGAL_PARAM.getCode(), e.getMessage());
}
@ExceptionHandler(ArithmeticException.class)
public <T> RespResult<T> handleMyException(ArithmeticException e) {
return RespResult.error(ErrorCodeEnum.ILLEGAL_PARAM.getCode(), "运算错误");
}
}
使用了@RestControllerAdvice注解之后,这个类就会去处理程序抛出的异常,但前提是需要配合@ExceptionHandler这个注解使用,这个注解需要传入一个异常类.class来捕获具体的异常,如@ExceptionHandler(MyException.class)就会拦截到MyException类型的异常,然后再将异常信息获取到,并封装到RespResult的返回里。
四、测试
@RestController
@RequestMapping
public class TestController {
@GetMapping("/ok")
public RespResult<String> ok() {
return RespResult.ok("hello world!");
}
@GetMapping("/err1")
public RespResult<List<String>> err1() {
List<String> list = new ArrayList<>();
list.add("name不能为空");
list.add("code不能为空");
return RespResult.error(ErrorCodeEnum.ILLEGAL_PARAM.getCode(), ErrorCodeEnum.ILLEGAL_PARAM.getMsg(), list);
}
@GetMapping("/err2")
public RespResult<Void> err2() {
throw MyExceptionFactory.create(ErrorCodeEnum.ILLEGAL_PARAM);
}
}
1.请求"/ok"
{
"code": "000",
"msg": "success",
"data": "hello world!"
}
2.请求"/err1"
{
"code": "001",
"msg": "参数错误",
"data": [
"name不能为空",
"code不能为空"
]
}
3.请求"/err2"
{
"code": "001",
"msg": "参数错误",
"data": null
}