文章目录
Spring Boot 全局异常处理问题分析与解决方案
问题背景
在 Spring Boot 项目中,全局异常处理器(GlobalExceptionHandler
)通常用于捕获和处理控制器中抛出的异常,并返回统一的响应格式。然而,当异常发生在过滤器(如 LoginCheckFilter
)中时,全局异常处理器可能无法捕获这些异常,导致返回的响应格式不符合预期。
问题现象
用户登录失败:
- 在控制器中抛出的
UserException
被全局异常处理器捕获,并返回自定义的Result
格式。 - 日志示例:
2025-09-03 17:50:54.948 ERROR 52880 --- [.0-8080-exec-10] c.f.g.exception.GlobalExceptionHandler : 用户错误信息:登录失败,用户名或密码错误
- 在控制器中抛出的
JWT 解析失败:
- 在过滤器中抛出的
UserException
未被全局异常处理器捕获,导致 Spring Boot 的默认错误处理机制接管,返回标准的错误响应。 - 日志示例:
2025-09-03 17:53:58.293 ERROR 52880 --- [0.0-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception com.financialfinshieldguard.gold.exception.UserException: jwt令牌无法正确解析
- 在过滤器中抛出的
问题分析
全局异常处理器的限制:
@RestControllerAdvice
和@ControllerAdvice
默认只捕获控制器方法中抛出的异常。对于过滤器中抛出的异常,这些注解不会生效。- 关键点:过滤器中的异常不会自动被全局异常处理器捕获。
过滤器中的异常处理:
- 在 Spring Boot 中,过滤器中的异常不会自动被全局异常处理器捕获。需要手动处理这些异常,并将其转换为自定义的响应格式。
解决方案
1. 在过滤器中手动处理异常
通过在过滤器中捕获异常,并手动调用全局异常处理器的方法来生成自定义的响应。
优点
- 灵活性高:可以在过滤器中对不同类型的异常进行精细控制。
- 一致性:确保过滤器中的异常也能返回与控制器中一致的响应格式。
缺点
- 代码重复:需要在每个过滤器中手动处理异常,可能导致代码重复。
- 维护成本高:如果全局异常处理逻辑发生变化,需要在多个地方更新代码。
示例代码
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
// 假设这里调用了 JwtUtil.parse 方法
chain.doFilter(request, response);
} catch (UserException e) {
// 手动调用全局异常处理器的方法
GlobalExceptionHandler globalExceptionHandler = new GlobalExceptionHandler();
Result<?> result = globalExceptionHandler.handleUserException(e);
// 设置响应内容类型和状态码
httpResponse.setContentType("application/json;charset=UTF-8");
httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// 写入自定义的响应内容
httpResponse.getWriter().write(result.toString());
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑
}
@Override
public void destroy() {
// 销毁逻辑
}
}
2. 使用 ErrorController
或 ErrorAttributes
通过实现 ErrorController
或自定义 ErrorAttributes
,可以捕获所有类型的异常,包括过滤器中抛出的异常,并返回自定义的响应格式。
优点
- 全局统一:可以统一处理所有异常,包括过滤器和控制器中的异常。
- 减少重复代码:避免在多个地方重复处理异常逻辑。
- 易于维护:全局异常处理逻辑集中在一个地方,便于维护和更新。
缺点
- 复杂性增加:实现
ErrorController
或ErrorAttributes
可能会增加代码的复杂性。 - 性能影响:全局异常处理可能会引入额外的性能开销,尤其是在高并发场景下。
示例代码
@RestController
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public ResponseEntity<Result<?>> handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.BAD_REQUEST.value()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Result.UserError(400, "jwt令牌无法正确解析"));
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Result.ServerError("未知错误"));
}
@Override
public String getErrorPath() {
return "/error";
}
}
3. 使用 AOP(面向切面编程)
通过 AOP 拦截器捕获过滤器和控制器中的异常,并统一处理。
优点
- 解耦:将异常处理逻辑与业务逻辑解耦,提高代码的可维护性和可读性。
- 灵活性:可以在不同的切点上应用不同的异常处理逻辑。
缺点
- 学习曲线:AOP 的概念和实现可能对一些开发人员来说有一定的学习曲线。
- 调试困难:AOP 代码的调试可能比普通代码更复杂。
示例代码
@Aspect
@Component
public class GlobalExceptionAspect {
@Around("execution(* com.financialfinshieldguard.gold.controller.*.*(..)) || execution(* com.financialfinshieldguard.gold.filter.*.*(..))")
public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (UserException e) {
Result<?> result = Result.UserError(e.getCode(), e.getMessage());
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
} catch (Throwable e) {
Result<?> result = Result.ServerError(e.getMessage());
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
4. 使用自定义的异常处理框架
一些企业会开发自己的异常处理框架,结合上述多种技术,提供统一的异常处理机制。
优点
- 定制化:可以根据企业的具体需求定制异常处理逻辑。
- 集成性:可以与现有的技术栈无缝集成,提供一致的异常处理体验。
缺点
- 开发成本:开发和维护自定义框架需要额外的资源和时间。
- 复杂性:自定义框架可能会增加系统的复杂性,特别是对于新加入的开发人员。
示例代码
// 自定义异常处理框架的实现代码示例
// 这里假设有一个自定义的异常处理框架类 CustomExceptionFramework
@Component
public class CustomExceptionFramework {
@Autowired
private GlobalExceptionHandler globalExceptionHandler;
public ResponseEntity<Result<?>> handleException(Throwable throwable) {
if (throwable instanceof UserException) {
UserException userException = (UserException) throwable;
return globalExceptionHandler.handleUserException(userException);
} else {
return globalExceptionHandler.handleException(throwable);
}
}
}
# 思:我得学一下AOP!!!