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中拆分到独立的配置类,从架构层面切断循环,完全不影响业务代码,符合单一职责原则。
- 新建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(); } }
- 删除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
相关产品推荐
相关产品推荐

