Spring循环依赖与@Async代理问题深度解析
2026/8/4 17:58:00 网站建设 项目流程

1. 项目概述

上周在重构一个老项目时,我遇到了一个典型的Spring循环依赖问题。表面上看只是常见的BeanCurrentlyInCreationException,但实际排查过程却意外地复杂。这个案例涉及@Async注解、自定义AgentService和Spring三级缓存的交互,最终花了整整两天时间才彻底解决。今天我就把这个排查过程完整记录下来,希望能帮到遇到类似问题的同行。

2. 问题现象与初步分析

2.1 异常堆栈解读

系统启动时抛出的异常堆栈如下:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Bean with name 'orderService' has been injected into other beans [...] in its raw version as part of a circular reference

这个异常表明Spring在创建orderService时检测到了循环依赖。但奇怪的是,这个服务在项目中已经稳定运行了两年,最近只是添加了几个@Async方法就突然出现了这个问题。

2.2 依赖关系图谱

通过IDE的依赖分析工具,我绘制出了关键的依赖关系:

orderService → paymentService → notificationService → asyncExecutor → orderService

这个环形依赖链中,asyncExecutor是Spring的异步任务执行器,notificationService通过@Async注解依赖它,而最终又绕回了orderService。

3. 循环依赖的深层原理

3.1 Spring三级缓存机制

Spring解决循环依赖的核心在于三级缓存:

  1. 一级缓存:存放完整初始化后的单例Bean
  2. 二级缓存:存放早期暴露的Bean引用(已实例化但未初始化)
  3. 三级缓存:存放Bean工厂对象(用于处理代理等情况)

当出现循环依赖时,Spring会通过提前暴露对象引用的方式打破循环。但某些特殊情况会破坏这个机制。

3.2 @Async的特殊处理

@Async注解会为方法创建AOP代理,这个代理的创建时机很关键:

  1. 普通Bean:在初始化完成后创建代理
  2. 循环依赖中的Bean:必须在早期暴露阶段就创建代理

如果代理创建时机不对,就会导致依赖注入的对象版本不一致。

4. 问题排查过程

4.1 代理对象验证

通过在构造器中打印this.getClass(),我发现:

// OrderService构造器输出: class com.example.OrderService$$EnhancerBySpringCGLIB$$12345678 // PaymentService中注入的orderService类型: class com.example.OrderService

这表明注入的实例和实际实例不是同一个代理对象,验证了代理创建时机的问题。

4.2 解决方案对比

我尝试了三种解决方案:

  1. 使用@Lazy注解
@Service public class PaymentService { @Lazy @Autowired private OrderService orderService; }

优点:快速解决启动问题缺点:只是延迟了问题发生时间,可能引发运行时异常

  1. 调整Bean加载顺序
@DependsOn({"paymentService", "notificationService"}) @Service public class OrderService {...}

结果:无效,因为循环依赖的本质未改变

  1. 重构代码结构(最终方案):
  • 将@Async方法提取到独立的AsyncNotificationService
  • 使用事件驱动模型解耦服务

5. 最佳实践建议

5.1 循环依赖设计原则

  1. 优先考虑重构代码结构,避免循环依赖
  2. 必须循环时,确保是setter注入而非构造器注入
  3. 避免在循环依赖链中使用AOP代理

5.2 @Async使用注意事项

  1. 异步方法最好放在专门的Service中
  2. 避免在循环依赖的Bean上使用@Async
  3. 考虑使用ApplicationEvent实现异步通知

6. 扩展思考:Spring代理机制

6.1 JDK动态代理 vs CGLIB

在解决这个问题时,我注意到Spring会根据类是否实现接口选择代理方式:

  • 实现接口:JDK动态代理
  • 未实现接口:CGLIB

可以通过配置强制使用CGLIB:

spring.aop.proxy-target-class=true

6.2 代理对象的识别技巧

在调试时,可以通过这些方法识别代理对象:

  1. 打印对象的class名称(包含$$EnhancerBySpringCGLIB$$)
  2. 使用AopUtils工具类:
AopUtils.isAopProxy(bean) AopUtils.isCglibProxy(bean)

7. 性能优化建议

7.1 异步线程池配置

默认的SimpleAsyncTaskExecutor不适合生产环境,建议配置:

@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("Async-Executor-"); executor.initialize(); return executor; } }

7.2 循环依赖监控

添加健康检查端点监控循环依赖:

management.endpoint.health.show-details=always management.health.circuitbreakers.enabled=true

8. 常见问题排查指南

8.1 典型错误场景

  1. 构造器注入循环依赖
@Service public class A { private final B b; public A(B b) { this.b = b; } } @Service public class B { private final A a; public B(A a) { this.a = a; } }

解决方案:改为setter注入

  1. @Async方法调用内部方法
public void methodA() { methodB(); // 不会走代理 } @Async public void methodB() {...}

解决方案:通过AopContext获取代理对象

8.2 调试技巧

  1. 启用Spring调试日志:
logging.level.org.springframework.beans=DEBUG logging.level.org.springframework.context=DEBUG
  1. 使用BeanPostProcessor打印Bean生命周期:
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("Before init: " + beanName); return bean; } }

9. 高级应用场景

9.1 自定义AgentService实现

在某些特殊场景下,可能需要自定义服务代理:

public interface AgentService<T> { T getAgent(); } @Service public class OrderAgentService implements AgentService<OrderService> { @Lazy @Autowired private OrderService orderService; @Override public OrderService getAgent() { return orderService; } }

9.2 结合Spring Cloud的使用

在微服务架构中,循环依赖可能跨越服务边界。这时可以考虑:

  1. 使用Feign客户端+熔断器
  2. 引入消息队列解耦
  3. 采用Saga模式管理分布式事务

10. 源码级分析

10.1 DefaultSingletonBeanRegistry解析

Spring处理循环依赖的核心类:

// 三级缓存定义 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存 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) { // 检查三级缓存 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; }

10.2 AbstractAutowireCapableBeanFactory关键方法

Bean实例化的核心流程:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // 1. 实例化 BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); // 2. 添加到三级缓存(解决循环依赖关键步骤) addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 3. 属性注入 populateBean(beanName, mbd, instanceWrapper); // 4. 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }

11. 性能优化深度实践

11.1 循环依赖对启动时间的影响

通过JProfiler测试发现:

  • 每个循环依赖会增加约50-100ms的启动时间
  • 深度循环依赖(A→B→C→A)比直接循环(A↔B)影响更大

优化建议:

  1. 使用@DependsOn明确依赖关系
  2. 将非必要依赖改为懒加载
  3. 拆分大型配置类

11.2 内存占用分析

循环依赖会导致:

  1. 更长的对象保留在创建中状态
  2. 三级缓存占用额外内存
  3. 可能的内存泄漏风险(如果循环依赖中包含大对象)

检测方法:

// 在BeanPostProcessor中记录创建中的Bean Set<String> creatingBeans = Collections.newSetFromMap(new ConcurrentHashMap<>()); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { creatingBeans.add(beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { creatingBeans.remove(beanName); return bean; }

12. 替代方案探讨

12.1 使用事件驱动模型

重构后的通知流程:

// 事件定义 public class OrderCompletedEvent { private Order order; // getters/setters } // 发布事件 @Service public class OrderService { @Autowired ApplicationEventPublisher eventPublisher; public void completeOrder(Order order) { // 业务逻辑 eventPublisher.publishEvent(new OrderCompletedEvent(order)); } } // 异步处理 @Service public class NotificationService { @Async @EventListener public void handleOrderCompleted(OrderCompletedEvent event) { // 发送通知 } }

12.2 采用响应式编程

使用Spring WebFlux重构:

@Service public class ReactiveOrderService { private final ReactivePaymentService paymentService; public Mono<Order> createOrder(Order order) { return paymentService.validatePayment(order) .then(notificationService.sendNotification(order)) .thenReturn(order); } }

13. 生产环境经验总结

13.1 监控指标设置

建议监控这些关键指标:

  1. Bean创建时间(特别是循环依赖中的Bean)
  2. 代理对象创建数量
  3. 异步任务队列积压情况

配置示例:

management.metrics.enable.spring.beans=true management.metrics.enable.spring.aop=true

13.2 压力测试要点

测试循环依赖系统时需要关注:

  1. 高并发下的死锁风险
  2. 内存泄漏迹象
  3. 代理对象创建的性能开销

测试方案:

@SpringBootTest @ActiveProfiles("test") public class CircularDependencyStressTest { @Autowired ApplicationContext context; @Test void testConcurrentAccess() { IntStream.range(0, 1000).parallel().forEach(i -> { OrderService service = context.getBean(OrderService.class); assertNotNull(service); }); } }

14. 工具链推荐

14.1 分析工具

  1. Spring Boot Actuator

    • /beans端点查看Bean依赖关系
    • /health端点检查循环断路器状态
  2. VisualVM

    • 分析Bean实例的内存占用
    • 跟踪代理对象的创建过程

14.2 开发辅助

  1. IDE插件

    • IntelliJ IDEA的Spring Assistant
    • Eclipse的Spring Tools Suite
  2. 代码检查工具

    • SonarQube的Spring规则集
    • ArchUnit测试架构约束

15. 未来演进方向

随着Spring框架的发展,循环依赖处理也在不断优化:

  1. Spring Boot 3.0对启动流程的改进
  2. GraalVM原生镜像对循环依赖的限制
  3. 响应式编程对传统依赖模式的改变

对于新项目,建议:

  1. 优先考虑响应式编程模型
  2. 采用模块化设计减少耦合
  3. 利用Spring Boot的自动配置优化

这个案例给我的深刻教训是:看似简单的循环依赖问题,在结合了@Async等高级特性后,会变得异常复杂。关键在于理解Spring容器的工作原理,并合理设计应用架构。当遇到类似问题时,建议从代理机制、缓存层级和Bean生命周期三个维度综合分析。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询