Spring框架循环依赖问题成因及@Lazy注解解决原理咨询
这问题确实挺让人困惑的——明明Spring号称能处理循环依赖,咋还会报错呢?我结合你的依赖链和@Lazy的作用,帮你拆解下可能的原因:
先明确Spring默认能处理的循环依赖范围
Spring的三级缓存机制,只支持单例Bean的字段注入/setter注入循环依赖:它会在Bean实例化(调用构造器)之后、属性填充之前,提前暴露Bean的代理对象来打破循环。但如果是以下几种情况,默认机制就会失效:
1. 循环依赖的Bean用了构造器注入
看你的依赖链,最终循环出现在categoryServiceImpl和demandServiceImpl之间。如果这两个Bean是通过构造器互相注入的,那Spring的三级缓存就帮不上忙了:
- 构造器调用是Bean实例化的第一步,这时候Bean还没被创建出来,没法提前暴露代理;
- 当Spring尝试初始化
categoryServiceImpl时,需要先实例化demandServiceImpl,而初始化demandServiceImpl又需要categoryServiceImpl,直接形成死循环。
而@Lazy的作用在这里就体现了:它会让Spring给其中一个Bean创建一个代理对象,注入到另一个Bean的构造器里,真正的Bean实例化会推迟到第一次调用方法的时候,彻底打破了初始化阶段的循环链。
2. 循环依赖的Bean不是单例作用域
如果categoryServiceImpl或demandServiceImpl是prototype(原型)作用域,Spring默认是不处理原型Bean的循环依赖的——因为原型Bean每次获取都要新建实例,没法通过缓存提前暴露对象。
这时候加@Lazy,会延迟原型Bean的创建时机:直到你第一次调用这个Bean的方法时,Spring才会去实例化它,避免了初始化集合(AuthenticateProcessor注入List<AuthenticateService>)时就触发循环创建。
3. 集合注入触发了提前初始化的特殊场景
你的AuthenticateProcessor注入的是List<AuthenticateService>,Spring在处理这种集合注入时,会自动扫描所有实现了AuthenticateService的Bean并初始化它们。如果categoryServiceImpl和demandServiceImpl的初始化顺序刚好导致Spring的三级缓存没来得及处理它们的循环(比如其中一个Bean的初始化过程中触发了另一个的完整初始化,而不是提前暴露代理),也会出现循环依赖报错。@Lazy同样通过延迟初始化解决了这个问题。
为啥其他场景复现不了?
这很可能是因为其他场景的循环依赖刚好符合Spring默认处理的条件:单例、字段/setter注入,或者循环链中没有集合这种会触发批量初始化的节点。而你的场景刚好踩中了默认机制的“盲区”。
内容的提问来源于stack exchange,提问作者libra_bing

