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

Spring应用上下文Bean出现循环依赖@Lazy及Setter注入均失效如何解决

Spring循环依赖问题解决

根因分析

首先明确循环依赖的完整闭环:

  • SecurityConfig实例化需要注入UserDetailsService(即你实现的UserServiceImpl)
  • UserServiceImpl实例化需要注入JwtManager
  • JwtManager实例化需要注入RSAPrivateKey、RSAPublicKey两个Bean
  • 而RSAPrivateKey、RSAPublicKey是定义在SecurityConfig内部的@Bean方法,要生成这两个Bean必须先完成SecurityConfig本身的实例化

四个环节形成闭环,导致启动报错。你之前修改Setter注入报错是因为没有把JwtManager从构造函数参数中移除,Spring仍然尝试通过构造注入该参数,因此抛出UnsatisfiedDependencyException。

解决方案

方案1(最推荐):拆分配置类

将JWT相关的密钥、解码器Bean从SecurityConfig中拆分到独立的配置类,从架构层面切断循环,完全不影响业务代码,符合单一职责原则。

  1. 新建JwtConfig配置类,把相关代码迁移过去:
@Configuration
public class JwtConfig {
    private final Logger LOG = LoggerFactory.getLogger(getClass());

    @Value("${app.security.jwt.keystore-location}")
    private String keyStorePath;
    @Value("${app.security.jwt.keystore-password}")
    private String keyStorePassword;
    @Value("${app.security.jwt.key-alias}")
    private String keyAlias;
    @Value("${app.security.jwt.private-key-passphrase}")
    private String privateKeyPassphrase;

    @Bean
    public KeyStore keyStore() {
        try {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream resourceAsStream = Thread.currentThread().getContextClassLoader()
                  .getResourceAsStream(keyStorePath);
            keyStore.load(resourceAsStream, keyStorePassword.toCharArray());
            return keyStore;
        } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
            LOG.error("Unable to load keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load keystore");
    }

    @Bean
    public RSAPrivateKey jwtSigningKey(KeyStore keyStore) {
        try {
            Key key = keyStore.getKey(keyAlias, privateKeyPassphrase.toCharArray());
            if (key instanceof RSAPrivateKey) {
                return (RSAPrivateKey) key;
            }
        } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) {
            LOG.error("Unable to load private key from keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load private key");
    }

    @Bean
    public RSAPublicKey jwtValidationKey(KeyStore keyStore) {
        try {
            Certificate certificate = keyStore.getCertificate(keyAlias);
            PublicKey publicKey = certificate.getPublicKey();
            if (publicKey instanceof RSAPublicKey) {
                return (RSAPublicKey) publicKey;
            }
        } catch (KeyStoreException e) {
            LOG.error("Unable to load private key from keystore: {}", keyStorePath, e);
        }
        throw new IllegalArgumentException("Unable to load RSA public key");
    }

    @Bean
    public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {
        return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();
    }
}
  1. 删除SecurityConfig中对应的JWT相关@Bean方法和@Value字段即可。

方案2:指定位置加@Lazy注解

你之前@Lazy未生效是因为加的位置不对,直接在SecurityConfig构造函数的UserDetailsService参数前加@Lazy即可:

public SecurityConfig(@Lazy UserDetailsService userService,
                      PasswordEncoder bCryptPasswordEncoder, ObjectMapper mapper) {
    this.userService = userService;
    this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    this.mapper = mapper;
}

原理是Spring会先注入UserDetailsService的代理对象,完成SecurityConfig的实例化,生成RSA密钥、JwtManager等Bean,待第一次真正调用UserService方法时才会初始化真实的UserServiceImpl实例,避开循环。

方案3:改为Setter注入

注意要先把JwtManager从构造函数参数中移除,避免构造注入报错:

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository repository;
    private final UserTokenRepository userTokenRepository;
    private final PasswordEncoder bCryptPasswordEncoder;
    @Lazy
    @Autowired
    private JwtManager tokenManager;


    public UserServiceImpl(UserRepository repository, UserTokenRepository userTokenRepository,
                           PasswordEncoder bCryptPasswordEncoder) {
        this.repository = repository;
        this.userTokenRepository = userTokenRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }
    // 原有业务方法保持不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 17:15:07