Spring循环依赖与解决方案全攻略
在Spring应用开发过程中,循环依赖(Circular Dependency)是一个既常见又棘手的问题。当Bean A依赖Bean B,同时Bean B又依赖Bean A时,就会形成经典的循环依赖场景。这种看似简单的依赖关系背后,隐藏着Spring框架精妙的设计哲学和复杂的解决机制。
一、循环依赖的本质矛盾
在传统Java开发中,对象间的循环依赖会导致严重的初始化问题:
class ServiceA {
private ServiceB serviceB;
// 构造器注入
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
class ServiceB {
private ServiceA serviceA;
// 构造器注入
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
这种情况下,无论是先实例化ServiceA还是ServiceB,都会陷入死循环。Spring通过三级缓存机制巧妙地解决了这个难题,但这种解决方案本身也存在边界条件。
二、Spring的三级缓存解密
Spring容器内部维护着三个重要的缓存容器:
- singletonObjects(一级缓存):存储完全初始化好的Bean
- earlySingletonObjects(二级缓存):存储提前暴露的原始Bean
- singletonFactories(三级缓存):存储Bean的ObjectFactory
Bean的创建过程遵循严格的生命周期:
// 简化版的Bean创建流程
protected Object doCreateBean(...) {
// 1. 实例化
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 2. 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(...));
// 3. 属性填充
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化
exposedObject = initializeBean(...);
return exposedObject;
}
三、构造器注入的循环依赖困局
当使用构造器注入时,循环依赖会直接导致BeanCurrentlyInCreationException:
// 构造器注入导致的循环依赖示例
@Component
public class CircularA {
private final CircularB circularB;
@Autowired
public CircularA(CircularB circularB) {
this.circularB = circularB;
}
}
@Component
public class CircularB {
private final CircularA circularA;
@Autowired
public CircularB(CircularA circularA) {
this.circularA = circularA;
}
}
Spring启动时会抛出:
Requested bean is currently in creation: Is there an unresolvable circular reference?
这是因为构造器注入需要在实例化阶段就完成依赖注入,而此时Bean尚未放入三级缓存。
四、Setter注入的破局之道
改用Setter方法注入可以解决这个问题:
@Component
public class CircularA {
private CircularB circularB;
@Autowired
public void setCircularB(CircularB circularB) {
this.circularB = circularB;
}
}
@Component
public class CircularB {
private CircularA circularA;
@Autowired
public void setCircularA(CircularA circularA) {
this.circularA = circularA;
}
}
这种方式的可行性来源于:
- 实例化与属性注入分离
- 提前暴露对象引用
- 分阶段完成依赖注入
五、复杂场景下的解决方案
5.1 @Lazy注解的巧妙运用
@Component
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Lazy注解创建代理对象,延迟真实依赖的解析时机。
5.2 ApplicationContextAware接口
@Component
public class ServiceA implements ApplicationContextAware {
private ApplicationContext context;
public void doSomething() {
ServiceB serviceB = context.getBean(ServiceB.class);
}
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
}
5.3 ObjectProvider智能选择
@Component
public class ServiceA {
private final ObjectProvider<ServiceB> serviceBProvider;
public ServiceA(ObjectProvider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
public void execute() {
ServiceB serviceB = serviceBProvider.getIfAvailable();
}
}
六、源码级的深度解析
分析DefaultSingletonBeanRegistry的关键代码:
public class DefaultSingletonBeanRegistry ... {
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
这段代码揭示了三级缓存的协同工作机制,以及Spring如何通过状态标记来检测循环依赖。
七、循环依赖的副作用与规避策略
-
AOP代理的时序问题
@Component public class ServiceA { @Autowired private ServiceB serviceB; @Transactional public void methodA() { // 事务注解可能失效 } }
当存在循环依赖时,AOP代理的创建时机可能导致注解失效。
-
设计模式改进方案
- 引入中间层(Mediator Pattern)
- 使用事件驱动(Event-Driven)
- 领域拆分(Domain Separation)
-
架构层面的预防措施
graph TD A[Controller层] --> B[Service层] B --> C[Manager层] C --> D[DAO层] D --> E[DB] style B fill:#f9f,stroke:#333 style C fill:#ccf,stroke:#333
通过分层架构约束依赖方向,形成严格的依赖层级。
八、Spring Boot中的特殊处理
在Spring Boot 2.6+版本中,可以通过配置关闭循环依赖支持:
spring.main.allow-circular-references=false
这个配置背后对应着Spring的AbstractAutowireCapableBeanFactory
:
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
九、性能视角的循环依赖分析
通过JProfiler进行内存分析时,循环依赖可能导致:
- 对象图复杂度指数级增长
- GC Roots引用链过长
- 序列化/反序列化问题
- 缓存失效风险增加
十、新型解决方案探索
-
模块化改造
@Configuration @Import({ModuleAConfig.class, ModuleBConfig.class}) public class MainConfig {}
-
响应式编程范式
@Service public class ReactiveServiceA { private final ReactiveServiceB serviceB; public Mono<String> process() { return serviceB.getData().flatMap(...); } }
-
GraalVM原生镜像支持 在构建原生镜像时,循环依赖会导致更严重的初始化问题,需要特别处理。
结语:循环依赖的双面性
循环依赖既是设计缺陷的警示灯,也是框架能力的试金石。开发者应该:
- 理解底层解决机制
- 在架构设计阶段规避不必要的循环
- 合理使用框架提供的解决方案
- 建立代码健康度监控机制