Spring Boot 全局异常处理问题分析与解决方案
文章目录
- Spring Boot 全局异常处理问题分析与解决方案
- 问题背景
- 问题现象
- 问题分析
- 解决方案
- 1. 在过滤器中手动处理异常
- 优点
- 缺点
- 示例代码
- 2. 使用 `ErrorController` 或 `ErrorAttributes`
- **优点**
- **缺点**
- **示例代码**
- 3. 使用 AOP(面向切面编程)
- **优点**
- **缺点**
- **示例代码**
- 4. 使用自定义的异常处理框架
- **优点**
- **缺点**
- **示例代码**
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 {@Overridepublic 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());}}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 初始化逻辑}@Overridepublic 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("未知错误"));}@Overridepublic 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 {@Autowiredprivate 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!!!