连载中 12/18

循环依赖进阶:@Lazy 能救,构造器救不了

2026-11-08 · 41 阅读 · 0 评论 · 0 赞

A 依赖 B,B 依赖 A:报错从哪来

上上篇讲过三级缓存:实例化 → 提前暴露工厂 → 属性注入。注意顺序——能救循环依赖的前提,是对象先实例化成功。换成构造器注入,故事在第一步就终结:A 创建时要先拿到 B 当构造参数,B 创建时要先拿到 A,两个都卡在实例化,缓存里什么都没来得及放,Spring 只能抛 BeanCurrentlyInCreationException。记住这个判据:能过实例化这一关的循环,三级缓存都能救;过不了的,谁也救不了。

Boot 2.6 的转折:默认禁止

Spring Boot 2.6 起,循环依赖默认禁止(spring.main.allow-circular-references 默认 false),启动直接失败并打印引用链。为什么收紧?因为循环依赖多数是设计问题的遮羞布——两个类互相调来调去,职责早就搅在一起了。老项目升级后启动炸一片,多半就是这个默认值在作怪。开关可以打开,但那是续命不是治病,先看下面三招怎么拆。

三招实战拆解

第一招 @Lazy,应急首选。在注入点标 @Lazy,Spring 注入的不是真 Bean 而是一个代理,首次调用方法时才去容器里拿真身——把「创建时就要」改成「第一次用时才要」,时机错开,环自然解开。这和三级缓存的工厂思想同源:都是延迟交付。

@Service
public class OrderService {

    private final InventoryService inventoryService;

    // 注入的是延迟代理,首次调用方法时才真正初始化
    public OrderService(@Lazy InventoryService inventoryService) {
        this.inventoryService = inventoryService;
    }
}

第二招 ObjectProvider,更体面的延迟。不要具体对象,要一个「提供者」,用的时候才 getObject()——语义清晰,还能遍历拿所有实现,适合「有这个依赖但不急」的场景。

@Service
public class OrderService {

    private final ObjectProvider<InventoryService> inventory;

    public OrderService(ObjectProvider<InventoryService> inventory) {
        this.inventory = inventory;
    }

    public void create() {
        // 用的时候才解析依赖
        inventory.getObject().lockStock();
    }
}

第三招重构,根治。循环依赖九成是职责没拆干净:把两个类共用的逻辑抽成第三个类 C,A 和 B 都依赖 C,环断成链。别嫌土,这是唯一不欠技术债的解法。

死结为什么解不开

把构造器的死结钉死在原理上:三级缓存的救命点是「实例化之后、属性注入之前」把工厂塞进缓存——前提是对象已经 new 出来了。构造器注入里,new 这一步本身就需要依赖在场,鸡生蛋蛋生鸡,缓存没有出场机会。口诀:字段与 setter 的循环靠缓存或 @Lazy,构造器的循环只能拆类或延迟。另外上上篇提过的 @Async 是特例,早期暴露的原始对象和最终代理对不上号,字段注入的环它也救不了——这种环别救,拆。

下一篇换个解耦姿势:Spring 事件机制。一次发布、多处响应,还有 @TransactionalEventListener 解决「事务没提交就收到通知」的时序暗坑。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#Spring#循环依赖#Lazy#ObjectProvider

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10134 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9899 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9329 阅读 · 21 评论 · 287 赞