前言

在我们做Web应用时,处理请求过程难免会发生一些错误,例如我们在做JSON处理时出现的JSONException,做文件处理时出现的IOException,以及一些开发时考虑不严谨或代码不规范所产生的NullPointerException等。在这种情况下,SpringBoot提供了一个默认的/error映射,例如:
image.png
或者:
image.png
那既然这样,我们为什么还要做统一异常处理呢?原因很简单,上面的提示,对于用户或者前端来说不是很友好,用户在得到这样的异常信息后或许并不知道是什么出现错误的原因。因此,我们需要一个错误信息更加完善的返回结果。
有人可能会说了,在上一篇文章中,不是已经证明了try-catch对性能影响并不大吗?那我直接在处理请求时加上try不就好了吗?
这样的确可以,但是会增加许多的工作量,并且代码的整洁、美观性也会下降,在多人协作的项目中,也很难做到统一的异常返回,即使定义了返回规范,但也可能会出现标点、表达不统一的情况。因此,我们做统一异常处理主要有以下两点因素:

  1. 使错误信息更加完善,对前端/用户更加友好。
  2. 提升代码的简洁程度,降低重复代码工作。
  3. 统一的返回格式。

了解了统一异常处理,我们现在就开始来实现它,在SpringBoot应用中,我们主要通过使用@ControllerAdvice和@ExceptionHandler注解的方式和使用ErrorController类两种方式来实现。

使用注解的方式实现

0x01创建异常处理器

首先,我们创建一个类并使用@ControllerAdvice修饰,并在类中添加对应的异常处理方法:

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
    protected static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public R error(Exception e){
        logger.error(e.getMessage());
        return R.error().message("未知错误").data("error",e.getMessage());
    }
}

首先,我们用@ControllerAdvice注解对类进行修饰,使SpringBoot能够自动扫描加载这个类,由于我们返回的是json格式的字符串,因此我们在这里添加了@ResponseBody注解,在方法中,我们使用了@ExceptionHandler注解捕获了Exception错误,并在方法中对该错误进行了处理以及返回统一的返回对象R。
此时我们再次访问控制器,查看返回结果:
image.png
我们看到,现在Handler成功的捕获到了异常,并返回了我们想要的格式。

0x02处理其他类型错误

在上面的代码,我们只对Exception进行了异常处理,也就是说,无论发生什么异常,都只会进行Exception的返回及处理,显然这不是我们想要的结果,我们想要的是更加详细的异常信息,因此,我们再添加一个对JSONException异常进行处理的方法:

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
    protected static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public R error(Exception e){
        logger.error(e.getMessage());
        return R.error().message("未知错误").data("error",e.getMessage());
    }

    @ExceptionHandler(JSONException.class)
    public R jsonException(Exception e){
        logger.error(e.getMessage());
        return R.error().message("解析输入数据失败").data("error",e.getMessage());
    }
}

此时我们再次访问控制器,查看返回结果:
image.png
我们发现,在Handler中,优先对JSONException进行了异常捕获并处理。

使用ErrorController类进行统一异常处理

在SpringBoot项目中,系统默认的异常处理类为BasicErrorController,最终会返回上面所说的/error映射页面,我们在项目中编写一个JSONErrorController类并实现ErrorController接口,这样在getErrorPath()方法会重定向我们编写的JSONErrorController

@Slf4j
@RestController
public class HttpErrorController implements ErrorController {
    private final static String ERROR_PATH = "/error";
    protected static final Logger logger = LoggerFactory.getLogger(HttpErrorController.class);
    @RequestMapping(path  = ERROR_PATH )
    public R error(HttpServletRequest request, HttpServletResponse response){
       logger.error("访问/error" + " 错误代码:"  + response.getStatus());
       return R.error().message().data("error",response.getStatus());
    }
    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

这样,我们再次访问控制器,便可以看到实现了统一异常处理
image.png