Spring Boot 全局异常处理问题分析与解决方案

文章目录

Spring Boot 全局异常处理问题分析与解决方案

问题背景

在 Spring Boot 项目中,全局异常处理器(GlobalExceptionHandler)通常用于捕获和处理控制器中抛出的异常,并返回统一的响应格式。然而,当异常发生在过滤器(如 LoginCheckFilter)中时,全局异常处理器可能无法捕获这些异常,导致返回的响应格式不符合预期。

问题现象

  1. 用户登录失败

    • 在控制器中抛出的 UserException 被全局异常处理器捕获,并返回自定义的 Result 格式。

    • 日志示例

      复制代码
      2025-09-03 17:50:54.948 ERROR 52880 --- [.0-8080-exec-10] c.f.g.exception.GlobalExceptionHandler   : 用户错误信息:登录失败,用户名或密码错误
  2. 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令牌无法正确解析

问题分析

  1. 全局异常处理器的限制

    • @RestControllerAdvice@ControllerAdvice 默认只捕获控制器方法中抛出的异常。对于过滤器中抛出的异常,这些注解不会生效。
    • 关键点:过滤器中的异常不会自动被全局异常处理器捕获。
  2. 过滤器中的异常处理

    • 在 Spring Boot 中,过滤器中的异常不会自动被全局异常处理器捕获。需要手动处理这些异常,并将其转换为自定义的响应格式。

解决方案

1. 在过滤器中手动处理异常

通过在过滤器中捕获异常,并手动调用全局异常处理器的方法来生成自定义的响应。

优点
  • 灵活性高:可以在过滤器中对不同类型的异常进行精细控制。
  • 一致性:确保过滤器中的异常也能返回与控制器中一致的响应格式。
缺点
  • 代码重复:需要在每个过滤器中手动处理异常,可能导致代码重复。
  • 维护成本高:如果全局异常处理逻辑发生变化,需要在多个地方更新代码。
示例代码
java 复制代码
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. 使用 ErrorControllerErrorAttributes

通过实现 ErrorController 或自定义 ErrorAttributes,可以捕获所有类型的异常,包括过滤器中抛出的异常,并返回自定义的响应格式。

优点
  • 全局统一:可以统一处理所有异常,包括过滤器和控制器中的异常。
  • 减少重复代码:避免在多个地方重复处理异常逻辑。
  • 易于维护:全局异常处理逻辑集中在一个地方,便于维护和更新。
缺点
  • 复杂性增加 :实现 ErrorControllerErrorAttributes 可能会增加代码的复杂性。
  • 性能影响:全局异常处理可能会引入额外的性能开销,尤其是在高并发场景下。
示例代码
java 复制代码
@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 代码的调试可能比普通代码更复杂。
示例代码
java 复制代码
@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. 使用自定义的异常处理框架

一些企业会开发自己的异常处理框架,结合上述多种技术,提供统一的异常处理机制。

优点
  • 定制化:可以根据企业的具体需求定制异常处理逻辑。
  • 集成性:可以与现有的技术栈无缝集成,提供一致的异常处理体验。
缺点
  • 开发成本:开发和维护自定义框架需要额外的资源和时间。
  • 复杂性:自定义框架可能会增加系统的复杂性,特别是对于新加入的开发人员。
示例代码
java 复制代码
// 自定义异常处理框架的实现代码示例
// 这里假设有一个自定义的异常处理框架类 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!!!
相关推荐
SunnyDays10113 小时前
Java 攻克 PDF 表格数据提取:从棘手挑战到自动化实践
java·提取pdf表格·读取pdf表格数据·pdf表格转csv·导出pdf表格为csv
初学小白...3 小时前
泛型-泛型方法
java·开发语言
LQ深蹲不写BUG3 小时前
深挖三色标记算法的底层原理
java·算法
上官浩仁3 小时前
springboot knife4j 接口文档入门与实战
java·spring boot·spring
bobz9653 小时前
spine leaf 组网架构:leaf 和 spine 之间的链路 mtu 普遍都是 9000
后端
bobz9653 小时前
arp 广播带 vlan id 么?
后端
optimistic_chen4 小时前
【Java EE进阶 --- SpringBoot】Spring IoC
spring boot·后端·spring·java-ee·mvc·loc
启山智软4 小时前
商城源码后端性能优化:JVM 参数调优与内存泄漏排查实战
java·电子商务·商城开发
wuk9984 小时前
在Spring MVC中使用查询字符串与参数
java·spring·mvc