什么异常能被 @RestControllerAdvice 或 @ControllerAdvice 捕获,什么情况不能被捕获?

Posted 石头wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么异常能被 @RestControllerAdvice 或 @ControllerAdvice 捕获,什么情况不能被捕获?相关的知识,希望对你有一定的参考价值。

一、什么异常能被 @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,如下图是其源码

以上是关于什么异常能被 @RestControllerAdvice 或 @ControllerAdvice 捕获,什么情况不能被捕获?的主要内容,如果未能解决你的问题,请参考以下文章

不是所有的Spring Boot异常都能被统一异常处理 | Java Debug 笔记

异常处理

异常类

异常处理

PG异常状态详解及故障总结

@ExceptionHandler处理异常