2025年07月14日/ 浏览 6
当两个Bean通过构造器互相引用时,Spring容器会抛出BeanCurrentlyInCreationException
。典型场景如:
java
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // ← 构造器依赖ServiceB
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { // ← 同时依赖ServiceA
this.serviceA = serviceA;
}
}
此时Spring陷入”鸡生蛋蛋生鸡”的死循环:初始化A需要先初始化B,但初始化B又需要A。
通过分析DefaultSingletonBeanRegistry
源码,Bean创建分为三个阶段:
1. 实例化:调用构造器创建原始对象
2. 属性填充:通过反射注入依赖
3. 初始化:执行@PostConstruct
等方法
构造器循环依赖发生在第一阶段,此时连原始对象都未完成创建,无法通过常规方式解决。
java
@Service
public class ServiceA {
@Autowired // 改为字段注入
private ServiceB serviceB;
}
代价:牺牲了构造器注入的不可变性和明确依赖关系优势。
java
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB; // 实际注入代理对象
}
}
原理:Spring生成代理对象暂时代替真实Bean,首次调用时才触发初始化。
java
@Service
public class ServiceA implements ApplicationContextAware {
private ServiceB serviceB;
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.serviceB = ctx.getBean(ServiceB.class);
}
}
适用场景:需要精确控制Bean获取时机时使用。
引入中间层解耦:
java
public interface IService {}
@Service
public class ServiceA implements IService {
private final IService serviceB;
public ServiceA(@Qualifier("serviceB") IService serviceB) {...}
}
java
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ServiceB {...}
通过CGLIB代理打破循环,但会带来性能开销。
@SpringBootTest
验证解决方案有效性 这与Spring的生命周期设计哲学有关:构造器阶段必须保证对象完整可用。官方文档明确建议避免循环依赖,将其视为设计警告而非特性。
通过理解这些方案背后的原理,开发者不仅能解决问题,更能提升对Spring IoC容器的深度认知。在实际项目中,建议结合SonarQube等工具主动检测循环依赖,保持代码健康度。