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解决循环依赖的核心在于三级缓存:
- 一级缓存:存放完整初始化后的单例Bean
- 二级缓存:存放早期暴露的Bean引用(已实例化但未初始化)
- 三级缓存:存放Bean工厂对象(用于处理代理等情况)
当出现循环依赖时,Spring会通过提前暴露对象引用的方式打破循环。但某些特殊情况会破坏这个机制。
3.2 @Async的特殊处理
@Async注解会为方法创建AOP代理,这个代理的创建时机很关键:
- 普通Bean:在初始化完成后创建代理
- 循环依赖中的Bean:必须在早期暴露阶段就创建代理
如果代理创建时机不对,就会导致依赖注入的对象版本不一致。
4. 问题排查过程
4.1 代理对象验证
通过在构造器中打印this.getClass(),我发现:
// OrderService构造器输出: class com.example.OrderService$$EnhancerBySpringCGLIB$$12345678 // PaymentService中注入的orderService类型: class com.example.OrderService这表明注入的实例和实际实例不是同一个代理对象,验证了代理创建时机的问题。
4.2 解决方案对比
我尝试了三种解决方案:
- 使用@Lazy注解:
@Service public class PaymentService { @Lazy @Autowired private OrderService orderService; }优点:快速解决启动问题缺点:只是延迟了问题发生时间,可能引发运行时异常
- 调整Bean加载顺序:
@DependsOn({"paymentService", "notificationService"}) @Service public class OrderService {...}结果:无效,因为循环依赖的本质未改变
- 重构代码结构(最终方案):
- 将@Async方法提取到独立的AsyncNotificationService
- 使用事件驱动模型解耦服务
5. 最佳实践建议
5.1 循环依赖设计原则
- 优先考虑重构代码结构,避免循环依赖
- 必须循环时,确保是setter注入而非构造器注入
- 避免在循环依赖链中使用AOP代理
5.2 @Async使用注意事项
- 异步方法最好放在专门的Service中
- 避免在循环依赖的Bean上使用@Async
- 考虑使用ApplicationEvent实现异步通知
6. 扩展思考:Spring代理机制
6.1 JDK动态代理 vs CGLIB
在解决这个问题时,我注意到Spring会根据类是否实现接口选择代理方式:
- 实现接口:JDK动态代理
- 未实现接口:CGLIB
可以通过配置强制使用CGLIB:
spring.aop.proxy-target-class=true6.2 代理对象的识别技巧
在调试时,可以通过这些方法识别代理对象:
- 打印对象的class名称(包含$$EnhancerBySpringCGLIB$$)
- 使用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=true8. 常见问题排查指南
8.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注入
- @Async方法调用内部方法:
public void methodA() { methodB(); // 不会走代理 } @Async public void methodB() {...}解决方案:通过AopContext获取代理对象
8.2 调试技巧
- 启用Spring调试日志:
logging.level.org.springframework.beans=DEBUG logging.level.org.springframework.context=DEBUG- 使用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的使用
在微服务架构中,循环依赖可能跨越服务边界。这时可以考虑:
- 使用Feign客户端+熔断器
- 引入消息队列解耦
- 采用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)影响更大
优化建议:
- 使用@DependsOn明确依赖关系
- 将非必要依赖改为懒加载
- 拆分大型配置类
11.2 内存占用分析
循环依赖会导致:
- 更长的对象保留在创建中状态
- 三级缓存占用额外内存
- 可能的内存泄漏风险(如果循环依赖中包含大对象)
检测方法:
// 在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 监控指标设置
建议监控这些关键指标:
- Bean创建时间(特别是循环依赖中的Bean)
- 代理对象创建数量
- 异步任务队列积压情况
配置示例:
management.metrics.enable.spring.beans=true management.metrics.enable.spring.aop=true13.2 压力测试要点
测试循环依赖系统时需要关注:
- 高并发下的死锁风险
- 内存泄漏迹象
- 代理对象创建的性能开销
测试方案:
@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 分析工具
Spring Boot Actuator:
- /beans端点查看Bean依赖关系
- /health端点检查循环断路器状态
VisualVM:
- 分析Bean实例的内存占用
- 跟踪代理对象的创建过程
14.2 开发辅助
IDE插件:
- IntelliJ IDEA的Spring Assistant
- Eclipse的Spring Tools Suite
代码检查工具:
- SonarQube的Spring规则集
- ArchUnit测试架构约束
15. 未来演进方向
随着Spring框架的发展,循环依赖处理也在不断优化:
- Spring Boot 3.0对启动流程的改进
- GraalVM原生镜像对循环依赖的限制
- 响应式编程对传统依赖模式的改变
对于新项目,建议:
- 优先考虑响应式编程模型
- 采用模块化设计减少耦合
- 利用Spring Boot的自动配置优化
这个案例给我的深刻教训是:看似简单的循环依赖问题,在结合了@Async等高级特性后,会变得异常复杂。关键在于理解Spring容器的工作原理,并合理设计应用架构。当遇到类似问题时,建议从代理机制、缓存层级和Bean生命周期三个维度综合分析。