一、什么异常能被 @RestControllerAdvice 或 @ControllerAdvice 捕获,什么情况不能被捕获?
1、能捕获到的异常
以下发生异常的情况,都是请求进入controller方法前发生的,即如果在所请求的endpoint首行打断点,是不会停住的,因为是进入到断点前的时机发生的。下列这些情况是能被这两个注解捕获到的
- 请求的Method错误:如GET/POST…
- 请求时未传必填参数 @RequestParam(required=true)
- 请求的参数转换错误:如字串无法转为整型、布尔类型、日期类型
- 请求的Content-Type错误
- 请求参数jsr303错误:即hibernate validator校验出来的@NotNull、@NotBlank、@NotEmpty、@Min、@Max、@Size、@Pattern…
(以上所有的点都实际测试过!)
进入controller方法后的一切异常自然是能够被上述两个注解捕获到的。
2、不能捕获到的异常
-
域名、IP写错或端口写错都不会得到任何status code
-
域名、IP和端口写正确,但endpoint路径写错,返回 404 的status code
这种情况为什么还能返回status code?因为域名(或IP)和端口都写对了,说明有进程在监听着,这个进程就是Java进程,我们的例子使用springboot,而springboot自然会接收这个请求,只不过因为没有匹配的endpoint,所以返回了404以及下面的错误提示 {"timestamp":"2022-10-04T06:19:07.930+00:00","status":404,"error":"Not Found","path":"/testEndpointNotExist"} 这个其实就是springboot帮你返回的,有明显的特征,即timestamp/status/error/path四个标志性的字段 由于还未进入任何一个endpoint,所以上述两个注解也没法拦截到,只是springboot拦截到了而已。
3、另一种不一定能进入这两个注解的情况
如果你的endpoint被nginx代理,当endpoint处理时间过长,当超过nginx设置的超时时间,此时不管endpoint后续执行成功亦或失败,代理都已经将结果返回给调用者了。
后续如果endpoint执行成功则不进入上述两个注解,否则进入。但无论如何,调用者接收到的status code为504,另外接收到的内容也不是JSON了,是一个html网页内容。
- nginx代理springboot的一个endpoint
4、定时任务、单元测试
4.1 定时任务
你写了一个定时任务,无论你的定时任务调用controller的这个endpoint,还是直接调用service层的方法,这种情况都会绕过这两个注解。
很好理解,这两个注解是拦截HTTP请求的,而定时任务的调用属于Java方法调用,根本不是http请求,所以肯定拦截不了这种情况。
4.2 单元测试
有时候单元测试,会直接调用controller的这个endpoint,或者直接调用service层的方法,这个跟上面的情况一样,这种情况如果发生异常也是会绕过这两个注解的。同样的道理,因为这种调用不是发起http请求故不会拦截异常
如果单元测试你使用MockMvc的话,如果发生异常还是能拦截到的,虽然是Mock,但毕竟是模拟http请求
@Test
public void testHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/hello")
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.param("name", "Tom"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Hello Tom!"))
.andDo(MockMvcResultHandlers.print());
}
二、@RestControllerAdvice 或 @ControllerAdvice 是唯一的选择吗?
在全局处理异常的时候,我们思考几个问题
- 这两个注解,比起AOP拦截controller方法、javax.servlet.Filter拦截controller方法、springboot的Interceptor拦截controller方法,谁先谁后?顺序问题!
- 上面提到有一种,即endpoint不存在的时候进入不了上述两个注解,能否有什么方法解决?参考本博客下一篇:https://blog.csdn.net/w8y56f/article/details/127166432
三、附录
1、全局处理controller异常的类是怎么写的
如下是本次实验的异常处理类的写法,供参考:捕获到异常后返回ResultBean的 JSON 对象,保证一旦捕获,status code也是200(不能被这个处理器捕获的就不一定是200了)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Throwable.class)
public ResultBean handleException(Throwable t, HttpServletResponse response) throws Throwable {
return ResultBean.fail(BizCode.FAIL, StackTraceGetter.getStackTrace(t));
}
}
2、@RestControllerAdvice 和 @ControllerAdvice 区别是什么
区别是,前者相当于是返回 JSON 版本的后者,即 @ResponseBody + @ControllerAdvice,如下图是其源码