一、问题背景
在一个 Spring Boot 项目中,我为系统接入了多个业务入口 ,这些入口都会依赖一个统一的服务类(AgentService),用于对外调用 AI 服务。
在功能逐步拓展后,项目启动时报错,核心异常如下:
BeanCurrentlyInCreationException:
Bean has been injected in its raw version as part of a circular reference,
but has eventually been wrapped
初看像是一个普通的循环依赖问题,但随着排查深入,发现事情并不简单。
二、第一反应:多个类依赖同一个 Bean,真的会有问题吗?
在项目中:
-
多个业务 Service 都依赖同一个
AgentService -
这些 Service 之间并没有直接互相注入
这类结构在 Spring 中是完全合法的,而且之前也一直运行正常。
所以第一时间就能判断:
问题不在"多个类依赖同一个 Bean"本身
三、从异常链路入手,定位真正的依赖路径
我开始认真分析 Spring 启动时给出的 依赖创建链路,发现了一个非常关键的信息:
AgentService 被注入到多个业务 Service 中,
但在创建过程中最终被"包裹(wrapped)"了
这说明两点:
-
Spring 在创建
AgentService时,中途就被其他 Bean 使用了 -
AgentService最终需要生成代理对象(AOP)
这已经不是"普通字段注入"的问题了。
四、关键突破口:为什么 Spring 一定要"最终成品"?
Spring 的循环依赖在很多情况下是可以解决的,这是大家都知道的事实。
那为什么这次不行?
原因在于:
👉 被代理的 Bean(如 @Async、@Transactional)不能随便用半成品
而我在 AgentService 中发现了这样一个方法:
@Async
public void someAsyncMethod(...) {
...
}
这意味着:
-
Spring 必须为该 Bean 创建 AOP 代理
-
代理只能作用在 最终完成的 Bean 上
-
一旦别的 Bean 在此之前拿到了"原始对象",Spring 就会直接报错
五、验证假设:最小成本试验
为了验证是否真的是 @Async 导致的问题,我并没有立即重构代码,而是先做了一件事:
临时注释掉
@Async注解
这是一个非常低成本、可回滚的试验。
结果:
✅ 应用顺利启动,循环依赖错误消失
这一步,基本可以确认根因。
六、问题本质总结
这次问题并不是:
-
多个平台接入
-
多个类依赖同一个 Service
-
WebClient 或远程调用问题
而是一个典型但容易被忽略的 Spring 行为差异:
普通 Bean 可以使用"半成品"解决循环依赖
但需要 AOP 代理的 Bean(如 @Async)必须是最终成品
一旦这两种需求同时存在,Spring 就只能选择 失败而不是冒险。
七、经验与反思
通过这次排查,我有几点比较深刻的收获:
-
不要急着"拆依赖"
- 先判断 Spring 为什么不帮你解决
-
异常信息本身就包含答案
- 特别是
raw version、wrapped这类关键词
- 特别是
-
AOP 是循环依赖的高危放大器
@Async、@Transactional都需要格外注意
-
验证假设,比"拍脑袋改代码"更重要
八、结语
Spring 的循环依赖问题,从来不是"会不会",而是:
"在什么前提下,Spring愿不愿意帮你兜底"