一、不同业务层处理异常的思路
- Repository 层出现异常或许可以忽略,或许可以降级,或许需要转化为一个友好的异常。如果一律捕获异常仅记录日志,很可能业务逻辑已经出错,而用户和程序本身完全感知不到
- Service 层往往涉及数据库事务,出现异常同样不适合捕获,否则事务无法自动回滚。此外 Service 层涉及业务逻辑,有些业务逻辑执行中遇到业务异常,可能需要在异常后转入分支业务流程。如果业务异常都被框架捕获了,业务功能就会不正常
- 如果下层异常上升到 Controller 层还是无法处理的话。Controller 层往往会给予用户友好提示,或是根据每一个 API 的异常表返回指定的异常类型,同样无法对所有异常一视同仁
二、如何定义合格的统一异常处理
对于自定义的业务异常,以 Warn 级别的日志记录异常以及当前 URL、执行方法等信息后,提取异常中的错误码和消息等信息,转换为合适的 API 包装体返回给 API 调用方;对于无法处理的系统异常,以 Error 级别的日志记录异常和上下文信息(比如 URL、参数、用户 ID)后,转换为普适的“服务器忙,请稍后再试”异常信息,同样以 API 包装体返回给调用方
@RestControllerAdvice
@Slf4j
public class RestControllerExceptionHandler {
private static int GENERIC_SERVER_ERROR_CODE = 2000;
private static String GENERIC_SERVER_ERROR_MESSAGE = "服务器忙,请稍后再试";
@ExceptionHandler
public APIResponse handle(HttpServletRequest req, HandlerMethod method, Exception ex) {
if (ex instanceof BusinessException) {
BusinessException exception = (BusinessException) ex;
log.warn(String.format("访问 %s -> %s 出现业务异常!", req.getRequestURI(), method.toString()), ex);
return new APIResponse(false, null, exception.getCode(), exception.getMessage());
} else {
log.error(String.format("访问 %s -> %s 出现系统异常!", req.getRequestURI(), method.toString()), ex);
return new APIResponse(false, null, GENERIC_SERVER_ERROR_CODE, GENERIC_SERVER_ERROR_MESSAGE);
}
}
}
三、 其他注意点
禁止生吞异常
生吞异常也就是直接丢弃异常不记录、不抛出。不管是你认为多么不重要的异常,都不应该生吞,哪怕是一个日志也好,因为被生吞掉的异常一旦导致Bug,就很难在程序中找到蛛丝马迹,使得 Bug 排查工作难上加难
禁止丢弃异常的原始信息
丢弃了异常的原始信息容易让我们无法精准锁定异常
比如:
private void readFile() throws IOException {
Files.readAllLines(Paths.get("a_file"));
}
@GetMapping("wrong1")
public void wrong1(){
try {
readFile();
} catch (IOException e) {
//只保留了异常消息,栈没有记录
log.error("文件读取错误, {}", e.getMessage());
//原始异常信息丢失
throw new RuntimeException("系统忙请稍后再试");
}
}
我们的日志就成了这个样子,谁能说这个是什么错吗?
[12:57:19.746] [http-nio-45678-exec-1] [ERROR] [.g.t.c.e.d.HandleExceptionController:35 ] - 文件读取错误 , a_file
通常我们建议采用下面的方式记录异常信息
catch (IOException e) {
log.error("文件读取错误", e);
throw new RuntimeException("系统忙请稍后再试");
}
禁止抛出异常时不指定任何消息
throw new RuntimeException();
[13:25:18.031] [http-nio-45678-exec-3] [ERROR] [c.e.d.RestControllerExceptionHandler:24 ] - 访问 /handleexception/wrong3 -> org…..demo1.HandleExceptionController#wrong3(String) 出现系统异常!java.lang.RuntimeException: null...
这里的 null 非常容易引起误解。按照空指针问题排查半天才发现,其实是异常的 message 为空