什么是循环依赖? 循环依赖(Circular Dependency)是指两个或多个 Bean 互相依赖,形成了一个闭环。
示例 1 2 Bean A → 依赖 Bean B → 依赖 Bean C → 依赖 Bean A ↑___________________________|
这种依赖关系形成一个环,导致 Bean 无法正常创建。
为什么循环依赖是个问题? 在创建 Bean 时,Spring 需要按照以下步骤:
实例化 :通过构造器创建对象
属性填充 :注入依赖的 Bean
初始化 :执行初始化方法(如 @PostConstruct)
放入缓存 :Bean 创建完成,放入单例池
如果是循环依赖,步骤 2 需要依赖的 Bean 还没创建完成,就会陷入”鸡生蛋、蛋生鸡”的问题。
常见的循环依赖场景 1. 构造器注入循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Component public class OrderService { private final UserService userService; public OrderService (UserService userService) { this .userService = userService; } public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { private final OrderService orderService; public UserService (OrderService orderService) { this .orderService = orderService; } public void updateUserPoints () { orderService.createOrder(); } }
运行结果 :
1 2 3 org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?
原因 :构造器注入需要在实例化时就获取完整的依赖对象,此时对象还没创建,无法提前暴露引用。
2. Setter 注入循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class OrderService { private UserService userService; @Autowired public void setUserService (UserService userService) { this .userService = userService; } public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { private OrderService orderService; @Autowired public void setOrderService (OrderService orderService) { this .orderService = orderService; } public void updateUserPoints () { orderService.createOrder(); } }
运行结果 :✅ 可以正常运行
原因 :Setter 注入可以在对象实例化之后再注入依赖,Spring 可以提前暴露未初始化的 Bean 引用。
3. 字段注入循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class OrderService { @Autowired private UserService userService; public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { @Autowired private OrderService orderService; public void updateUserPoints () { orderService.createOrder(); } }
运行结果 :✅ 可以正常运行
原因 :同 Setter 注入,字段注入也是在实例化后通过反射设置属性值。
4. 自引用循环依赖 1 2 3 4 5 6 7 8 9 @Component public class SelfRefService { @Autowired private SelfRefService selfRefService; public void doSomething () { selfRefService.doSomething(); } }
运行结果 :✅ 可以正常运行(虽然这种设计很奇怪)
5. 复杂的多层循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component public class ServiceA { @Autowired private ServiceB serviceB; } @Component public class ServiceB { @Autowired private ServiceC serviceC; } @Component public class ServiceC { @Autowired private ServiceA serviceA; }
运行结果 :✅ 可以正常运行(Spring 可以处理任意长度的循环依赖链)
Spring 如何解决循环依赖? Spring 使用 三级缓存机制 来解决单例 Bean 的循环依赖。这个机制只在以下情况下有效:
Bean 是单例(singleton)
Bean 支持字段注入或 Setter 注入
循环依赖是通过属性注入形成的(不是构造器注入)
三级缓存详解 Spring 在 DefaultSingletonBeanRegistry 类中维护了三级缓存:
1 2 3 4 5 6 7 8 private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 );private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap <>(16 );private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap <>(16 );
缓存级别
变量名
存储内容
生命周期
用途
一级缓存
singletonObjects
完全初始化的 Bean
Bean 创建完成到应用销毁
存放已经完全创建好的单例 Bean,供其他 Bean 获取
二级缓存
earlySingletonObjects
早期暴露的 Bean
实例化后到初始化完成
存放已经实例化但未初始化的 Bean,用于解决循环依赖
三级缓存
singletonFactories
ObjectFactory
实例化时到获取早期引用
存放 Bean 的工厂,用于延迟创建早期引用
三级缓存的工作流程 假设有 ServiceA 和 ServiceB 互相依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 1:创建 ServiceA │ ├─────────────────────────────────────────────────────────────────┤ │ 1.1 实例化 ServiceA │ │ 1.2 将 ServiceA 的工厂放入三级缓存 │ │ singletonFactories.put("serviceA", () -> getEarlyReference) │ │ 1.3 标记 ServiceA 正在创建中 │ │ 1.4 填充属性,发现需要 ServiceB │ │ 1.5 从缓存中查找 ServiceB,未找到 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 2:创建 ServiceB │ ├─────────────────────────────────────────────────────────────────┤ │ 2.1 实例化 ServiceB │ │ 2.2 将 ServiceB 的工厂放入三级缓存 │ │ singletonFactories.put("serviceB", () -> getEarlyReference) │ │ 2.3 标记 ServiceB 正在创建中 │ │ 2.4 填充属性,发现需要 ServiceA │ │ 2.5 从缓存中查找 ServiceA │ │ 2.6 一级缓存、二级缓存都没有,但三级缓存有 ServiceA 的工厂 │ │ 2.7 调用工厂获取早期引用,放入二级缓存 │ │ earlySingletonObjects.put("serviceA", earlyReference) │ │ singletonFactories.remove("serviceA") │ │ 2.8 ServiceB 初始化完成 │ │ 2.9 将 ServiceB 放入一级缓存 │ │ singletonObjects.put("serviceB", serviceB) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 3:完成 ServiceA │ ├─────────────────────────────────────────────────────────────────┤ │ 3.1 继续填充 ServiceA 的属性,从二级缓存获取 ServiceB │ │ 3.2 ServiceA 初始化完成 │ │ 3.3 将 ServiceA 放入一级缓存 │ │ singletonObjects.put("serviceA", serviceA) │ │ 3.4 从二级缓存删除 ServiceA │ │ earlySingletonObjects.remove("serviceA") │ └─────────────────────────────────────────────────────────────────┘
为什么需要三级缓存? 问题 :为什么不能只用二级缓存?
回答 :三级缓存的目的是 延迟创建早期引用 ,只有当真正需要时才创建。这在以下场景很重要:
AOP 代理的场景 如果 ServiceA 需要被 AOP 代理(例如加了 @Transactional),那么注入到 ServiceB 的应该是代理对象,而不是原始对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component public class ServiceA { @Transactional public void methodA () { } } @Component public class ServiceB { @Autowired private ServiceA serviceA; public void methodB () { serviceA.methodA(); } }
Spring 会在三级缓存中存放一个 ObjectFactory,只有当循环依赖真正发生时,才调用工厂创建代理对象。如果没有循环依赖,就不会创建代理,节省资源。
源码分析 1. 添加三级缓存 1 2 3 4 5 6 7 8 9 protected void addSingletonFactory (String beanName, ObjectFactory<?> singletonFactory) { synchronized (this .singletonObjects) { if (!this .singletonObjects.containsKey(beanName)) { this .singletonFactories.put(beanName, singletonFactory); this .earlySingletonObjects.remove(beanName); } } }
2. 获取 Bean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Nullable protected Object getSingleton (String beanName, boolean allowEarlyReference) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this .earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this .singletonObjects) { singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null ) { singletonObject = this .earlySingletonObjects.get(beanName); if (singletonObject == null ) { ObjectFactory<?> singletonFactory = this .singletonFactories.get(beanName); if (singletonFactory != null ) { singletonObject = singletonFactory.getObject(); this .earlySingletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); } } } } } } return singletonObject; }
3. 创建 Bean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected Object doCreateBean (String beanName, RootBeanDefinition mbd, Object[] args) { Object bean = instanceWrapper.getWrappedInstance(); boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } populateBean(beanName, mbd, instanceWrapper); Object exposedObject = initializeBean(beanName, exposedObject, mbd); if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != bean) { exposedObject = earlySingletonReference; } } addSingleton(beanName, exposedObject); return exposedObject; }
为什么构造器注入无法解决循环依赖? 原因分析 构造器注入需要在 实例化阶段 就获取依赖对象,而此时:
对象还没创建,无法放入三级缓存
即使提前暴露引用,构造器参数也无法传入未完全初始化的对象
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class ServiceA { private final ServiceB serviceB; public ServiceA (ServiceB serviceB) { this .serviceB = serviceB; } } @Component public class ServiceB { private final ServiceA serviceA; public ServiceB (ServiceA serviceA) { this .serviceA = serviceA; } }
执行流程 :
创建 ServiceA
调用构造器 new ServiceA(serviceB) ← 需要 ServiceB,但 ServiceB 还没创建
创建 ServiceB
调用构造器 new ServiceB(serviceA) ← 需要 ServiceA,但 ServiceA 还没创建
死循环 ❌
对比 Setter/字段注入 1 2 3 4 5 6 7 8 9 @Component public class ServiceA { private ServiceB serviceB; @Autowired public void setServiceB (ServiceB serviceB) { this .serviceB = serviceB; } }
执行流程 :
实例化 ServiceA(通过无参构造器)
提前暴露 ServiceA 引用到三级缓存
填充属性,需要 ServiceB
从三级缓存获取 ServiceA 的引用
完成 ServiceB 的创建
ServiceB 注入到 ServiceA
ServiceA 创建完成 ✅
如何解决循环依赖? 方法 1:使用 @Lazy 注解(推荐) @Lazy 注解可以延迟 Bean 的初始化,只有真正使用时才创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Component public class OrderService { private final UserService userService; public OrderService (@Lazy UserService userService) { this .userService = userService; } public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { private final OrderService orderService; public UserService (@Lazy OrderService orderService) { this .orderService = orderService; } public void updateUserPoints () { orderService.createOrder(); } }
@Lazy 的工作原理 :
Spring 注入的不是真实的 UserService,而是一个代理对象
代理对象内部持有 UserService 的 Bean 名称
第一次调用方法时,代理对象才会从容器中获取真实的 UserService
源码分析 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected Object getLazyResolutionProxyIfNecessary (DependencyDescriptor descriptor, String beanName) { if (isLazy(descriptor)) { Object target = buildLazyResolutionProxy(descriptor, beanName); return target; } return null ; } protected Object buildLazyResolutionProxy (DependencyDescriptor descriptor, String beanName) { return Proxy.newProxyInstance( getClass().getClassLoader(), new Class <?>[] { descriptor.getDependencyType() }, new LazyTargetSourceProxy () { @Override protected Object resolveTarget () { return beanFactory.getBean(beanName); } } ); }
注意 :
@Lazy 只适用于单例 Bean
对于原型 Bean(@Scope("prototype")),@Lazy 不起作用
方法 2:改用 Setter 或字段注入 如果循环依赖无法避免,可以考虑将构造器注入改为 Setter 或字段注入。
1 2 3 4 5 6 7 8 9 @Component public class OrderService { private UserService userService; @Autowired public void setUserService (UserService userService) { this .userService = userService; } }
优点 :
简单直接,不需要修改太多代码
Spring 可以自动解决循环依赖
缺点 :
破坏了不可变性(final 字段无法使用)
依赖关系不明确,不知道需要哪些依赖
单元测试时无法通过构造器注入 Mock 对象
方法 3:重构代码(最佳实践) 循环依赖往往是设计问题的体现。最好的解决方案是 消除循环依赖 。
A. 抽取公共服务 将循环依赖的共同依赖抽取到一个新的 Service 中。
重构前 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class OrderService { @Autowired private UserService userService; public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { @Autowired private OrderService orderService; public void registerUser () { orderService.createOrder(); } }
重构后 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Component public class PointsService { public void addPoints (Long userId, int points) { } } @Component public class OrderService { @Autowired private PointsService pointsService; public void createOrder () { pointsService.addPoints(userId, 100 ); } } @Component public class UserService { @Autowired private PointsService pointsService; public void registerUser () { pointsService.addPoints(userId, 100 ); } }
B. 使用事件驱动模式 通过事件解耦,A 发送事件,B 监听事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class OrderCreatedEvent extends ApplicationEvent { private final Long userId; private final BigDecimal amount; public OrderCreatedEvent (Object source, Long userId, BigDecimal amount) { super (source); this .userId = userId; this .amount = amount; } } @Component public class OrderService { @Autowired private ApplicationEventPublisher eventPublisher; public void createOrder () { Order order = new Order (); eventPublisher.publishEvent(new OrderCreatedEvent (this , userId, amount)); } } @Component public class UserService { @EventListener @Async public void handleOrderCreated (OrderCreatedEvent event) { updateUserPoints(event.getUserId(), event.getAmount()); } }
优点 :
完全解耦
易于扩展(新增监听器不需要修改发送方)
支持异步处理
缺点 :
C. 使用接口隔离 通过接口打破循环依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface IOrderCreator { void createOrder () ; } public interface IUserUpdater { void updateUserPoints () ; } @Component public class OrderService implements IOrderCreator { @Autowired private IUserUpdater userUpdater; @Override public void createOrder () { userUpdater.updateUserPoints(); } } @Component public class UserService implements IUserUpdater { @Autowired private IOrderCreator orderCreator; @Override public void updateUserPoints () { orderCreator.createOrder(); } }
原理 :
OrderService 依赖的是接口 IUserUpdater,不是具体的 UserService
UserService 依赖的是接口 IOrderCreator,不是具体的 OrderService
通过接口形成松耦合
D. 使用依赖倒置原则(DIP) 将公共逻辑抽取到抽象层,让具体实现依赖抽象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public interface OrderHandler { void handle (Order order) ; } @Component public class PointsOrderHandler implements OrderHandler { @Override public void handle (Order order) { } } @Component public class NotificationOrderHandler implements OrderHandler { @Override public void handle (Order order) { } } @Component public class OrderService { @Autowired private List<OrderHandler> handlers; public void createOrder (Order order) { saveOrder(order); for (OrderHandler handler : handlers) { handler.handle(order); } } }
优点 :
符合开闭原则(对扩展开放,对修改关闭)
每个处理器职责单一
易于测试和维护
E. 使用 @PostConstruct 延迟初始化 通过 @PostConstruct 在 Bean 初始化完成后再设置循环依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Component public class OrderService { @Autowired private ApplicationContext context; private UserService userService; @PostConstruct public void init () { this .userService = context.getBean(UserService.class); } public void createOrder () { userService.updateUserPoints(); } } @Component public class UserService { @Autowired private ApplicationContext context; private OrderService orderService; @PostConstruct public void init () { this .orderService = context.getBean(OrderService.class); } public void updateUserPoints () { orderService.createOrder(); } }
注意 :这种方式不太优雅,不推荐使用。
方法 4:使用 @DependsOn 指定依赖顺序 @DependsOn 可以指定 Bean 的初始化顺序,但不能解决循环依赖问题。
1 2 3 4 5 6 @Component @DependsOn("userService") public class OrderService { @Autowired private UserService userService; }
注意 :这只是控制初始化顺序,不能解决循环依赖。如果两个 Bean 真的互相依赖,还是会报错。
特殊场景 1. 原型 Bean 的循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 @Component @Scope("prototype") public class ServiceA { @Autowired private ServiceB serviceB; } @Component @Scope("prototype") public class ServiceB { @Autowired private ServiceA serviceA; }
结果 :❌ 无法解决,Spring 不支持原型 Bean 的循环依赖。
原因 :原型 Bean 每次获取都会创建新实例,Spring 无法缓存早期引用。
2. 多例 Bean 的循环依赖 同原型 Bean,无法解决。
3. 单例 Bean 依赖原型 Bean 1 2 3 4 5 6 7 8 9 10 11 12 @Component public class ServiceA { @Autowired private ServiceB serviceB; } @Component @Scope("prototype") public class ServiceB { @Autowired private ServiceA serviceA; }
结果 :✅ 可以正常运行
原因 :ServiceA 是单例,ServiceB 是原型。每次创建 ServiceB 时,ServiceA 已经存在,可以直接注入。
4. AOP 代理的循环依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class ServiceA { @Transactional public void methodA () { } } @Component public class ServiceB { @Autowired private ServiceA serviceA; @Transactional public void methodB () { serviceA.methodA(); } }
结果 :✅ 可以正常运行,Spring 会注入代理对象。
性能影响 三级缓存的性能开销
额外内存 :每个 Bean 可能会同时在三级缓存中存在
同步锁 :getSingleton() 方法使用了 synchronized,会有一定的并发开销
代理创建 :如果涉及 AOP,需要创建代理对象
优化建议
避免不必要的循环依赖 :最好的优化是消除循环依赖
使用 @Lazy :可以减少不必要的依赖查找
合理使用 Scope :非必要不要使用原型 Bean
最佳实践 1. 优先使用构造器注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component public class OrderService { private final UserService userService; public OrderService (UserService userService) { this .userService = userService; } } @Component public class OrderService { @Autowired private UserService userService; }
优点 :
依赖关系明确
对象不可变
易于测试(可以直接传入 Mock 对象)
编译期检查依赖完整性
2. 避免循环依赖 循环依赖通常是设计问题的体现,应该通过重构来消除。
3. 使用依赖倒置原则 让高层模块和低层模块都依赖于抽象。
4. 合理使用事件驱动 对于解耦要求高的场景,考虑使用事件驱动模式。
5. 使用 @Lazy 作为临时解决方案 如果短期内无法重构,可以使用 @Lazy 临时解决。
常见问题 Q1: 为什么 Spring 不能解决构造器注入的循环依赖? A : 因为构造器注入需要在实例化时就获取依赖对象,而此时对象还没创建,无法提前暴露引用。只有通过字段注入或 Setter 注入,才能在实例化后注入依赖。
Q2: @Lazy 注解会带来什么问题? A :
延迟初始化可能导致运行时才发现依赖缺失
增加了系统的复杂度
单元测试时 Mock 对象的创建会更复杂
Q3: 三级缓存什么时候清理? A :
二级缓存在 Bean 完全初始化后清理
三级缓存在早期引用被创建后清理
应用关闭时,所有缓存都会被清理
Q4: 如何检测循环依赖? A :
启动时 Spring 会抛出 BeanCurrentlyInCreationException
可以使用工具(如 Spring Boot Actuator)查看 Bean 的依赖关系
可以使用 IDE 的依赖关系图插件
Q5: 循环依赖一定会导致问题吗? A : 不一定。如果使用 Setter 或字段注入,Spring 可以自动解决。但循环依赖仍然是设计问题,应该尽量避免。
总结
注入方式
能否解决循环依赖
原因
构造器注入
❌ 不能
实例化时就需要依赖对象,无法延迟
Setter 注入
✅ 能
可以先实例化,再注入依赖
字段注入
✅ 能
同 Setter 注入
原型 Bean
❌ 不能
每次创建新实例,无法缓存
核心要点 :
三级缓存 :Spring 通过三级缓存机制解决单例 Bean 的循环依赖
构造器注入 :无法解决循环依赖,应优先使用构造器注入,避免循环依赖
@Lazy :可以临时解决构造器注入的循环依赖,但不是最佳实践
重构设计 :循环依赖通常是设计问题,应该通过抽取公共服务、事件驱动等方式消除
性能考虑 :三级缓存有一定的性能开销,但不应该成为避免重构的理由
最佳实践 :
优先使用构造器注入
避免循环依赖,它是代码设计问题的体现
如果无法避免,优先考虑重构(抽取服务、事件驱动、接口隔离)
临时方案可以使用 @Lazy 注解
对于复杂系统,使用依赖倒置原则和事件驱动模式
参考链接