spring如何解决循环依赖:三级缓存机制

spring如何解决循环依赖,核心依靠三级缓存提前暴露半成品Bean,仅支持单例、setter注入场景,原型Bean、构造器注入、@Lazy未正确配置的单例Bean无法使用该方案,你可通过调整注入方式、使用@Lazy注解、重构代码拆分Bean职责三种手段处理循环依赖,三级缓存分别存放已实例化未填充属性的半成品Bean、Bean工厂对象、完成完整初始化的成品Bean,Bean创建时先放入一级缓存,填充属性阶段若检测到循环引用,就从二级缓存获取半成品对象,避免重复创建,BeanPostProcessor处理AOP代理对象时会借助三级缓存的ObjectFactory生成代理,防止注入原始裸对象。

spring循环依赖三级缓存结构

三级缓存是三个ConcurrentHashMap,各自存储不同状态的Bean实例,一级缓存singletonObjects保存完全初始化完毕的单例Bean,Bean创建完成后才存入;二级缓存earlySingletonObjects存放提前暴露的半成品Bean实例,已经完成实例化、属性还没填充;三级缓存singletonFactories存储Bean工厂ObjectFactory,用于生成Bean实例或者AOP代理对象。Bean开始实例化后,会向三级缓存存入ObjectFactory,属性填充阶段如果别的Bean需要引用当前对象,就调用ObjectFactory获取实例,放到二级缓存,之后直接复用二级缓存对象,不再重复调用工厂方法。

三级缓存的关键作用是支撑AOP代理。如果Bean需要动态代理,提前暴露阶段不能直接放原始对象,通过ObjectFactory延迟生成代理,保证循环依赖注入拿到的是代理对象,不是未增强的原始实例。只保留两级缓存可以解决普通非AOP场景循环依赖,但遇到需要AOP的Bean,会注入原始对象,引发业务逻辑异常。

spring循环依赖可行方案对比

解决方案适用场景开发成本潜在问题
setter注入+三级缓存单例Bean,非构造注入,存在AOP低构造注入场景失效
@Lazy延迟注入构造器注入场景,不想改动原有代码低会延迟依赖对象初始化时机
代码重构拆分Bean长期项目,循环依赖属于设计缺陷高改动量大,需要调整业务职责
原型Bean(不推荐)无合适场景,仅作了解低spring原生不处理原型循环依赖,直接抛出异常

spring循环依赖无法处理的边界场景

原型作用域Bean每次获取都会新建实例,spring不会缓存任何半成品对象,检测到循环依赖直接抛出BeanCurrentlyInCreationException。构造器注入场景下,Bean实例化前就需要传入依赖对象,此时对象还没创建,无法提前暴露半成品实例,三级缓存机制无法生效。单例Bean配合@DependsOn注解强制Bean创建顺序,两个Bean互相设置依赖顺序,也会触发循环依赖报错。

spring5.3版本官方文档明确说明,构造注入产生的循环依赖不属于框架可自动处理范围,需要开发者干预。

这是代码设计层面的问题。

你排查循环依赖异常时,优先看报错堆栈的BeanCurrentlyInCreationException,确认Bean作用域和注入方式,先判断是否属于三级缓存支持范围。如果是setter注入单例Bean出现该异常,大概率是AOP代理逻辑自定义BeanPostProcessor破坏了提前暴露的对象引用;如果是构造注入,直接切换@Lazy或者重构类结构。

使用@Lazy注解时,要把注解加到依赖参数上,不能只加到类上。注解加到参数上,注入的是代理占位对象,在第一次调用方法的时候才会去容器获取真实Bean实例,打断Bean创建时的循环等待链。只在类上加@Lazy,仅延迟当前Bean的创建时机,无法解决构造注入互相引用的循环依赖。

重构方案优先提取公共业务到第三个独立Bean,两个互相引用的Bean同时依赖新Bean,消除双向引用。该方案可以从根源消除循环依赖,降低后续维护成本,在中大型业务项目中优先选择。

敬慕百科汇集百科知识与游戏文化,带你发现世界的每一个精彩角落。

想要了解更多关于spring如何解决循环依赖的文章欢迎访问:百科