您好,登录后才能下订单哦!
# RestControllerAdvice无法捕获filter中抛出的异常问题
## 引言
在Spring Boot应用开发中,全局异常处理是一个非常重要的环节。Spring提供了`@RestControllerAdvice`注解来帮助我们优雅地处理控制器层抛出的异常。然而,许多开发者在实践中发现,当异常发生在Filter层时,`@RestControllerAdvice`却无法捕获这些异常。本文将深入探讨这个问题产生的原因,并提供多种解决方案。
## 一、问题现象描述
### 1.1 典型场景还原
假设我们有一个简单的Spring Boot应用,包含以下组件:
```java
// 自定义Filter
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 模拟权限校验失败
throw new AuthenticationException("Token验证失败");
}
}
// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<String> handleAuthException(AuthenticationException e) {
return ResponseEntity.status(401).body(e.getMessage());
}
}
当请求经过Filter时抛出异常:
- 期望:被GlobalExceptionHandler
捕获并返回401状态码
- 实际:返回500错误页面,异常未被捕获
@RestControllerAdvice
本质上是通过@ControllerAdvice
和@ResponseBody
的组合实现的,其底层依赖于Spring MVC的DispatcherServlet
异常处理流程:
DispatcherServlet
HandlerExceptionResolver
处理Filter属于Servlet规范的一部分,其执行顺序在DispatcherServlet
之前:
HTTP Request → Servlet Container → Filter Chain → DispatcherServlet → Controller
关键问题在于:
- Filter抛出异常时,请求尚未进入DispatcherServlet
- Spring MVC的异常处理机制尚未激活
- 异常直接由Servlet容器处理
异常发生位置 | 处理机制 | 能否被@RestControllerAdvice捕获 |
---|---|---|
Controller | Spring MVC异常处理体系 | 是 |
Filter | Servlet容器默认处理 | 否 |
Interceptor | Spring MVC处理流程 | 是 |
@RestController
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public ResponseEntity<ErrorResponse> handleError(HttpServletRequest request) {
Integer status = (Integer) request.getAttribute("javax.servlet.error.status_code");
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
if (exception != null) {
return ResponseEntity.status(status)
.body(new ErrorResponse(status, exception.getMessage()));
}
return ResponseEntity.status(status)
.body(new ErrorResponse(status, "Unknown error"));
}
}
优点: - 统一处理所有错误 - 兼容非Spring异常
缺点:
- 无法复用@ExceptionHandler
逻辑
- 需要手动处理异常信息
public class JwtFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// 业务逻辑
} catch (AuthenticationException e) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json");
httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpResponse.getWriter().write(
"{\"code\":401,\"message\":\"" + e.getMessage() + "\"}"
);
return;
}
chain.doFilter(request, response);
}
}
优点: - 响应速度快 - 处理逻辑直接
缺点: - 重复代码多 - 破坏统一异常处理体系
结合Filter异常捕获和Controller转发:
// Filter改造
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
chain.doFilter(request, response);
} catch (Exception e) {
request.setAttribute("filter.error", e);
request.getRequestDispatcher("/error/filter").forward(request, response);
}
}
}
// 新增Controller
@RestController
@RequestMapping("/error")
public class FilterErrorController {
@RequestMapping("/filter")
public ResponseEntity<?> handleFilterError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute("filter.error");
if (e instanceof AuthenticationException) {
return ResponseEntity.status(401).body(e.getMessage());
}
return ResponseEntity.status(500).body("Server error");
}
}
通过Spring的代理机制实现:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> customFilter() {
FilterRegistrationBean<DelegatingFilterProxy> registration =
new FilterRegistrationBean<>();
registration.setFilter(new DelegatingFilterProxy("customFilter"));
registration.addUrlPatterns("/*");
return registration;
}
@Bean(name = "customFilter")
public Filter customFilter() {
return new CustomSpringAwareFilter();
}
}
public class CustomSpringAwareFilter extends OncePerRequestFilter {
@Autowired
private GlobalExceptionHandler exceptionHandler;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
chain.doFilter(request, response);
} catch (Exception e) {
ResponseEntity<?> entity = exceptionHandler.handleException(e);
// 转换ResponseEntity到HttpServletResponse
}
}
}
关注点分离:
异常分类处理:
方案 | 性能影响 | 适用场景 |
---|---|---|
ErrorController | 中等 | 需要统一错误页面 |
Filter直接处理 | 最优 | 简单API场景 |
异常转发 | 较差 | 需要复用异常逻辑 |
DelegatingFilterProxy | 中等 | 复杂Spring集成 |
sequenceDiagram
participant Client
participant Filter
participant DispatcherServlet
participant Controller
participant ExceptionHandler
Client->>Filter: HTTP Request
Filter->>DispatcherServlet: Forward
DispatcherServlet->>Controller: Handle Request
Controller->>ExceptionHandler: Throw Exception
ExceptionHandler->>Client: Error Response
DefaultHandlerExceptionResolver
HandlerExceptionResolverComposite
@ExceptionHandler
方法ResponseStatusExceptionResolver
Servlet Container
└── DispatcherServlet (Spring MVC)
└── Spring Application Context
关键点:Filter由Servlet容器直接管理,不经过Spring上下文
框架 | 处理方式 | 特点 |
---|---|---|
JAX-RS | ExceptionMapper | 统一但性能开销大 |
Node.js | 中间件错误处理 | 灵活但需要手动传播 |
Django | 中间件process_exception | 与Spring方案三类似 |
本文详细分析了@RestControllerAdvice
无法捕获Filter异常的深层原因,并提供了四种切实可行的解决方案。在实际项目中,建议根据具体需求选择:
理解Spring异常处理机制的本质,能够帮助我们在面对类似架构问题时做出更合理的设计决策。
”`
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
开发者交流群:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/hs798630734/blog/5023249