🚩 需求描述

将接口访问所有异常进行统一处理。在业务逻辑编写时,应该将所有可预知的异常定义好,并设定唯一的错误代码,在异常发生时,通过统一的返回值自动包装,提供给前端优化的错误提示,并能够通过错误代码快速定位异常类型。

🐘 异常对象

定义一个业务层异常,用于抛出业务逻辑上的异常错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ServiceException extends RuntimeException {

/**
* 错误信息
*/
public final String message;

/**
* 错误代码
*/
public final String code;

public static ServiceException of(ExceptionEnum exceptionEnum) {
return new ServiceException(exceptionEnum.code, exceptionEnum.message);
}
}

其中  ExceptionEnum  类是用于描述异常的枚举类型,定义异常的错误代码及错误信息。

1
2
3
4
5
6
@AllArgsConstructor
public enum ExceptionEnum {
;
public final String code;
public final String message;
}

📄 代码实现

通过  @RestControllerAdive  提升作用域,通过  @ExceptionHandler 注解来处理不同的异常。

🔢 业务层异常

所有的业务逻辑错误触发的异常。

1
2
3
4
5
6
7
8
9
10
/**
* 处理业务异常
*
* @param exception 异常
* @return 统一返回值
*/
@ExceptionHandler(ServiceException.class)
public RestResponse<String> handleServiceException(ServiceException exception) {
return RestResponse.error(exception.code, exception.message);
}

⁉ 未知异常

用于捕捉其他的所有不可预知的错误,可能是表字段的数据异常,导致数据库在更新时无法动态的生成 SQL(在编写时默认该值不为空,但被人为删除了),而产生的 dao 层异常,也有可能时系统资源被占用,导致文件无法写入的 IO 异常,不可控制的网络超时异常等等。

1
2
3
4
5
6
7
8
9
10
/**
* 其他未知的所有异常
*
* @param exception 异常
* @return 统一返回值
*/
@ExceptionHandler(Exception.class)
public RestResponse<String> handleOtherException(Exception exception) {
return RestResponse.error(exception.getMessage());
}

🔐 参数校验异常

需要使用 @Validated 注解标记在 Controller 的类、方法或参数上。对于验证规则,使用 javax.validation.constraintsorg.hibernate.validator.constraints 包中的规则注解。具体使用此处不细究,仅对异常的处理进行展开。
该异常表现为接口访问错误,但具体的错误提示信息并未写入到响应,服务端异常信息如下:

1
2
javax.validation.ConstraintViolationException: common.msg: 消息不能为空
...异常堆栈信息

可以建立一个指定异常为  ConstraintViolationException 的处理器,用于封装参数错误的异常信息。

1
2
3
4
@ExceptionHandler(ConstraintViolationException.class)
public RestResponse<String> constraintViolationExceptionHandler(ConstraintViolationException e) {
return RestResponse.error(((ConstraintViolation) e.getConstraintViolations().toArray()[0]).getMessage());
}

其中 ConstraintViolation 可能不止一个参数校验的结果,取第(任)一个即可。

🔍 404 异常

尽管 Spring 默认实现了 /error(即接口访问出错)情况下的处理,(在 Postman 上随手输出一个接口,模拟 404)如:

1
2
3
4
5
6
7
{
"timestamp": "2020-01-07T16:37:31.160+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/123"
}

但在某些时候,需要针对错误访问进行特殊的处理,此时可以实现 ErrorController 接口进行自定义拓展。
如下对 4xx、5xx 系列的错误状态进行处理:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("error")
public RestResponse<Object> errorHandle(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status.is4xxClientError()) {
return RestResponse.error("客户端访问出错");
}
if (status.is5xxServerError()) {
return RestResponse.error("服务器访问异常");
}
return RestResponse.error("未知异常");
}

当然,在条件分支中,对某个具体的状态做判断,可以更细致化的处理不同的访问错误。

其他方法

  1. @Exception 中进行状态判断,对错误请求做处理;
  2. (未测试)在资源目录(/resources/public/error)下添加错误访问页面,命名为 404.html、500.html 等;
  3. (未测试)配置 EmbeddedServletContainerCustomizer 实现;

评论