在SpringBoot应用开发中,利用@ControllerAdvice 结合 @ExceptionHandler来实现对后端服务的统一异常管理似乎已经成为了开发者约定俗成的规范了,为此网上也有很多文章来阐述如何更加优雅的来实现统一异常管理,但这样做真的好吗?
在SpringBoot中的全局统一异常管理通常是指利用@ControllerAdvice 结合 @ExceptionHandler来自定义一个全局的异常处理器,从而避免了在程序中频繁写书写try-catch来对异常进行处理。但结合笔者最近惨痛debug旧有项目的经历来看,笔者不推荐这样去做。在讨论之前我们不妨先来看看实际开发中都有哪些地方可能出现的异常。
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleGlobalException(Exception ex) { // 堆代码 duidaima.com // 在这里实现异常处理逻辑 log.error(ex.getMessage());//在控制台打印错误信息 return Result.error(ex.getMessage()); } }即我们通过在ExceptionHandler执行捕获异常为Exception来尽可能捕获业务中可能遇到的各种异常,相信网上已经有很多博主都在不断讲授这样的写法。
{ "message": “服务器异常”, "code":602 }而后台服务通常会打印出如下信息:
不难发现,通过网上所盛传全局异常处理逻辑,我根本不知道问题出在哪里。我只知道当前程序出现异常,且异常信息为/ by zero。除此之外我无法得到任何有用的信息。
此外,通过在@ExceptionHandler配置Exception.class使得程序可以捕获多种异常信息,但这样粗粒度做法所导致的直接问题就是无法精确的定位异常问题发生所在地,极大的提升了问题定位的难度。以笔者项目同事debug经历来看,当项目无法正常运行时,组内的程序员通常会通过打日志的方法来一行一行定位问题所在。
其实造成这样调试困难得本质原因在于全局异常机制的滥用,如下这张图真实的反映了引入全局异常机制后,异常的处理逻辑。不难发现,当引入全局异常处理后,所有的异常信息都会交由RestExceptionHandler来进行处理。当程序遇到异常时,会将异常信息抛给RestExceptionHandler来处理,并由其定义错误的处理逻辑。
分析到此处,其实你已经发现了。对于全局异常处理逻辑而言,其更适合做异常的兜底工作。即如果当前层出现异常,并且不断上抛的仍然无法解决的话,不妨通过全局统一的异常管理来进行处理,以对这些未处理的异常进行捕获。
此外,异常处理不应该进行像很多博客说的那样,仅是通过e.getMessage打印异常信息就可以了。这对于排查问题没有以一丁点的帮助,可以说是百害而无一利。
@Slf4j @RestControllerAdvice public class GlobExceptionHandler { @ExceptionHandler(ArithmeticException.class)//ArithmeticException异常类型通过注解拿到 public String exceptionHandler(HttpServletRequest request,ArithmeticException exception){ // 打印详细信息 log(request,exception); return exception.getMessage(); } public void log(HttpServletRequest request, Exception exception) { //换行符 String lineSeparatorStr = System.getProperty("line.separator"); StringBuilder exStr = new StringBuilder(); StackTraceElement[] trace = exception.getStackTrace(); // 获取堆栈信息并输出为打印的形式 for (StackTraceElement s : trace) { exStr.append("\tat " + s + "\r\n"); } //打印error级别的堆栈日志 log.error("访问地址:" + request.getRequestURL() + ",请求方法:" + request.getMethod() + ",远程地址:" + request.getRemoteAddr() + lineSeparatorStr + "错误堆栈信息如下:" + exception.toString() + lineSeparatorStr + exStr); } }
当程序发生错误时,其打印的日志信息如下:不难发现,其完整的打印出了url、方法信息,错误参数、请求地址等信息,极大的降低了线上Bug的排查难度。
技术本身并没有什么对与错之分,只不过有时我们用的方式和时机不对,进而使得本该提效的工具,反而在不断拖垮我们的效率。就如同本文分析的全局异常处理机制一样,其确实可以帮助我们降低try-catch的使用,但错误且不加考虑的乱用只会使得当系统出现问题时,我们只能两眼一抹黑,然后一行一行打日志来定位问题。