You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring过滤器注入HandlerExceptionResolver引发循环依赖问题求助

解决JwtAuthFilter中HandlerExceptionResolver的循环依赖与Bean冲突问题

问题背景

自定义JwtAuthFilter时,尝试注入HandlerExceptionResolver捕获异常,出现两个核心问题:

  1. 应用启动触发循环依赖报错
  2. 不添加@Qualifier("handlerExceptionResolver")会出现Bean实例冲突

原JwtAuthFilter代码

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String username = null;
        String token = null;

        try {
            String authHeader = request.getHeader("Authorization");
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                token = authHeader.substring(7);
                username = jwtService.extractUsername(token);
            }
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.validateToken(token, userDetails.getUsername())) {
                    UsernamePasswordAuthenticationToken authToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        } catch (Exception e){
            resolver.resolveException(request, response, null, e);
        }

        filterChain.doFilter(request, response);
    }
}

循环依赖报错

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  jwtAuthFilter (field private org.springframework.web.servlet.HandlerExceptionResolver com.example.j4.filter.JwtAuthFilter.resolver)
↑     ↓
|  org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
↑     ↓
|  securityConfig (field private com.example.j4.filter.JwtAuthFilter com.example.j4.security.SecurityConfig.authFilter)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Bean冲突报错

Field resolver in com.example.j4.filter.JwtAuthFilter required a single bean, but 2 were found:
    - errorAttributes: defined by method 'errorAttributes' in class path resource [org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.class]
    - handlerExceptionResolver: defined by method 'handlerExceptionResolver' in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]

解决方案

方案一:通过ApplicationContext手动获取HandlerExceptionResolver

实现ApplicationContextAware接口,在Bean初始化完成后从上下文获取指定实例,彻底打破循环依赖:

@Component
public class JwtAuthFilter extends OncePerRequestFilter implements ApplicationContextAware {
    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    private HandlerExceptionResolver resolver;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 明确获取名为handlerExceptionResolver的Bean
        this.resolver = applicationContext.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 原业务逻辑保持不变
        String username = null;
        String token = null;

        try {
            String authHeader = request.getHeader("Authorization");
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                token = authHeader.substring(7);
                username = jwtService.extractUsername(token);
            }
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.validateToken(token, userDetails.getUsername())) {
                    UsernamePasswordAuthenticationToken authToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
        } catch (Exception e){
            resolver.resolveException(request, response, null, e);
        }

        filterChain.doFilter(request, response);
    }
}

方案二:使用@Lazy延迟注入依赖

在HandlerExceptionResolver的注入注解上添加@Lazy,让Spring延迟初始化该依赖,避免循环依赖触发:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    @Qualifier("handlerExceptionResolver")
    @Lazy // 添加延迟注入注解
    private HandlerExceptionResolver resolver;

    // 原doFilterInternal方法不变
}

方案三:手动处理异常,脱离HandlerExceptionResolver依赖

如果不需要复用全局异常处理逻辑,可直接在catch块中构造自定义响应,完全避免对HandlerExceptionResolver的依赖:

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String username = null;
        String token = null;

        try {
            String authHeader = request.getHeader("Authorization");
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                token = authHeader.substring(7);
                username = jwtService.extractUsername(token);
            }
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (jwtService.validateToken(token, userDetails.getUsername())) {
                    UsernamePasswordAuthenticationToken authToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
            filterChain.doFilter(request, response);
        } catch (Exception e){
            // 手动构造JSON格式错误响应
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            writer.write("{\"code\":401,\"message\":\"" + e.getMessage() + "\"}");
            writer.flush();
            writer.close();
            // 异常处理完成后终止过滤器链
            return;
        }
    }
}

方案对比

  • 方案一:符合Spring Bean生命周期规范,彻底解决循环依赖,推荐生产环境使用。
  • 方案二:代码改动最小,仅需添加一个注解,适合快速临时解决问题。
  • 方案三:完全解耦,无需依赖Spring异常处理机制,适合需要自定义异常响应格式的场景。

内容的提问来源于stack exchange,提问作者milanHrabos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:10:35