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