前言
异常处理是项目开发中绕不过的一个环节,一个优雅的全局异常处理可以迅速反馈给开发人员这些信息
1、异常种类
2、可能导致异常的原因
3、导致异常出现的关键参数
4、异常发生的时间
5、发生异常的请求路径
这些信息有助于开发人员迅速定位、处理异常,一个优秀的项目应该尽可能的将可能发生的异常进行
捕获
,再通过自定义的处理流程将异常信息反馈,而不是一味的抛出
异常
异常捕获流程
ErrorCode 异常信息枚举
对于异常的处理我倾向于通过枚举类列举广义的异常种类,再通过附加信息对异常种类进行细分。
枚举类可以规范管理异常的种类,方面异常对象的构建,但是不利于异常种类的细化,比如
A
业务类抛出了C
资源不存在的错误,B
业务类同样抛出了D
资源不存在的错误,那么从广义上来说这两种异常都属于RESOURCE_NOT_FOUNT
异常,但是我们无法区分这个异常是因为C
资源不存在还是D
资源不存在而出现的异常。大多数开源项目更倾向于通过构建异常的Message
来对异常类型进行细分,而通过创建ResourceException
这样具体的异常类来对异常种类进行粗略的区分。所以我们可以通过构建异常Message
和枚举类
相结合的方式来弥补这一点。
@Getter
@AllArgsConstructor
public enum ErrorCode {
RESOURCE_NOT_FOUNT(1001, HttpStatus.HTTP_NOT_FOUND,"未找到该资源"),
PARAMS_FORMAT_INVALID(1002,HttpStatus.HTTP_BAD_REQUEST,"请求参数格式错误");
private final int code;
private final int httpStatus;
private final String message;
}
BaseException 基础异常对象
BaseException 是所有自定义异常的父类,它规范了异常的基本结构由 ErrorCode 和 Map(附加信息)组成
@Getter
@Setter
public class BaseException extends RuntimeException {
private final ErrorCode code;
private final Map<String,Object> detail = new HashMap<>();
public BaseException(ErrorCode code) {
this.code = code;
}
public BaseException(ErrorCode code,Map<String,Object> params) {
this.code = code;
if (!ObjectUtils.isEmpty(params)) {
this.detail.putAll(params);
}
}
public BaseException(ErrorCode code,String msg) {
this.code = code;
HashMap<String,String> map = new HashMap<>();
map.put("description",msg);
detail.putAll(map);
}
ErrorVO 异常视图对象
定义一个异常视图对象,构建异常的基本信息,比如时间,请求路径等,该对象接收一个 BaseException 异常类
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorVO {
//异常编码
private Integer code;
//http状态码
private Integer httpStatus;
//信息
private String message;
//发生异常的请求
private String path;
//异常发生时间戳
private Instant timestamp;
//异常附加信息
private Map<String, Object> detail = new HashMap<>();
public ErrorVO(BaseException e,String path) {
this.code = e.getCode().getCode();
this.httpStatus = e.getCode().getHttpStatus();
this.timestamp = Instant.now();
this.path = path;
this.message = e.getCode().getMessage();
if (ObjectUtils.isEmpty(e.getDetail())){
this.detail = null;
}else {
this.detail.putAll(e.getDetail());
}
}
}
全局返回对象 Result
我认为一个项目的请求返回对象应该做到全局统一,这样开发的时候就不用纠结到底该返回哪个对象
@Getter
@Setter
@AllArgsConstructor
public class Result<T> {
//操作成功(true)或者失败(false)
private boolean success;
//封装的数据对象,传入vo或者自定义的Exception类
private T data;
public static Result<Object> ok() {
return new Result<>(true,null);
}
public static Result<String> ok(String msg) {
return new Result<>(true,msg);
}
public static Result<Object> ok(Object data) {
return new Result<>(true,data);
}
public static Result<Object> error() {
return new Result<>(false,null);
}
public static Result<String> error(String msg) {
return new Result<>(false,msg);
}
public static Result<ErrorVO> error(ErrorVO error) {
return new Result<>(false,error);
}
}
异常捕获类
@ControllerAdvice
表明被注释的类是一个通知增强类,如果希望返回对象格式为 json 可以使用注解@RestControllerAdvice
,可以使用注解@ExceptionHandler
来匹配所有被@RequestMapping
注释的方法中抛出的异常,如果匹配成功,那么就按照自定义的流程去处理该异常
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public Result<ErrorVO> baseException(BaseException e, HttpServletRequest request) {
ErrorVO errorVO = new ErrorVO(e,request.getRequestURI());
return Result.error(errorVO);
}
}
异常捕获效果
测试类
测试采用三种方式来构建异常对象,一种是无附加信息,第二种是通过拼接 Message 返回附加信息,第三种是通过构建 Map 对象返回异常附加信息
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/resource")
public Result<Object> resource(){
throw new BaseException(ErrorCode.RESOURCE_NOT_FOUNT);
}
@GetMapping("/id")
public Result<Object> test(@Param("id")String id) {
if (id.length()>5){
throw new BaseException(ErrorCode.RESOURCE_NOT_FOUNT,"The invalid id is:\n" + id);
}else {
return Result.ok();
}
}
@PostMapping("/resource")
public Result<Object> resource(@RequestBody Car car){
throw new BaseException(ErrorCode.PARAMS_FORMAT_INVALID,
ImmutableMap.of("category",car.getCategory(),
"name",car.getName(),
"number",car.getNumber()));
}
}
效果展示
这三种返回信息基本满足了日常开发中对于异常信息的描述,当然对于异常的捕获处理一定还有更好的方式,这种方式只是我个人觉得比较方便,并不意味着这就是最好的方案了,欢迎大家补充
第一种方式
{
"success": false,
"data": {
"code": 1001,
"httpStatus": 404,
"message": "未找到该资源",
"path": "/test/resource",
"timestamp": "2020-06-29T19:25:40.138Z",
"detail": null
}
}
第二种方式
{
"success": false,
"data": {
"code": 1002,
"httpStatus": 400,
"message": "请求参数格式错误",
"path": "/test/id",
"timestamp": "2020-06-29T19:25:15.297Z",
"detail": {
"description": "The invalid id is: 1111111111"
}
}
}
第三种方式
{
"success": false,
"data": {
"code": 1001,
"httpStatus": 404,
"message": "未找到该资源",
"path": "/test/resource",
"timestamp": "2020-06-29T19:29:41.032Z",
"detail": {
"name": "ChangAn-1",
"number": "普A-123456",
"category": "smallCar"
}
}
}