Circular view path 异常导致 SpringBoot 应用内存溢出
🤔

Circular view path 异常导致 SpringBoot 应用内存溢出

Created
Jun 14, 2024 03:38 PM
Tags

☹️ 情景复现

 
有这样一个控制器:
@RestController @RequestMapping("/books") public class BookController { private final BookService bookService; public BookController(BookService bookService) { this.bookService = bookService; } @GetMapping("/all") public Response<List<Book>> getAllBooks() { List<Book> books = bookService.getAllBooks(); return Response.success(books); } }
请求 /books/all 可以正常得到结果,但是请求不存在的 /books/test 时,出现报错:
Circular view path [books]: would dispatch back to the current handler URL [/books] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
而且,等几秒后控制台还会打印大量的 /books/books/books/…/books/books…,最后内存爆满。
 
 

🔧 解决过程

 
查找一些文章
 
让我们使用 @RestController 注解(已使用),添加 spring-boot-starter-thymeleaf 依赖(没效果),询问 GPT,提醒我:
Spring MVC 尝试解析视图名称导致的无限循环。这种情况通常发生在 Spring 尝试将未映射的请求解析为视图名称时,特别是在没有配置合适的视图解析器或者返回视图名称不正确的情况下。
之后提醒我进行一些没用的配置。
 
视图?我的代码里没用涉及到,按理说不可能往视图运行,之前也尝试进行错误的调用,均没有遇到这种情况,这是第一次遇到。那到底是怎么回事呢?
 
我继续看相关的解决方法,将目光重新聚焦到 @RestController 注解上,有这样一个回答引起我的注意:
I had the same issue and I noticed that my controller was also annotated with @Controller. Replacing it with @RestController solved the issue. Here is the explanation from Spring Web MVC: 我遇到了同样的问题,我注意到我的控制器也被注释了 @Controller 。替换 @RestController 它解决了问题。以下是 Spring Web MVC 的解释:
@RestController is a composed annotation that is itself meta-annotated with @Controller and @ResponseBody indicating a controller whose every method inherits the type-level @ResponseBody annotation and therefore writes directly to the response body vs view resolution and rendering with an HTML template. @RestController 是一个组合注释,它本身带有 @Controller 和 @ResponseBody 元注释,指示一个控制器,其每个方法都继承类型级 @ResponseBody 注释,因此直接写入响应正文,而不是使用 HTML 模板进行视图解析和呈现。
 
我看到了熟悉的对象:视图。
我推测:大概的说,在某个地方,有类似 @Controller 注解标注的类将请求理解为视图请求,在默认生成的视图解决器上,因为生成了和 books 同名的视图名称,根据Spring的转发规则,就等于让自己转发给自己,会陷入死循环。
 
加之我尝试了其他控制器,都会有这个问题,也就是说,整个应用的控制器都会有这个问题,于是我找到了唯一的影响全局的全局异常处理器:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Response<Void> handleException(Exception ex) { return Response.error(Response.CODE.SERVER_ERROR); } }
 
注意到,这里使用了 @ControllerAdvice ,直觉告诉我,问题出现在这里。
我换成了 @RestControllerAdvice ,再次启动,问题解决,拦截到了异常并返回然后结束。
 
那换回 @ControllerAdvice 能否拦截到异常呢?结果实验,能够拦截
能够拦截,我们放行
能够拦截,我们放行
放行后居然又拦截到了一次,注意到 resourcePath 的变化,我们继续放行
放行后居然又拦截到了一次,注意到 resourcePath 的变化,我们继续放行
一点点变长
一点点变长
死循环
死循环
到这里,我们成功解决了问题,即想当然的使用不熟悉的注解 @ControllerAdvice ,我很想知道这里面的根本原因。
 
 

进一步查找原因

首先熟悉一下一个请求来到应用时的流程
notion image
(先睡觉去了,有空再继续研究)