一、不同业务层处理异常的思路
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 为空