Spring如何解决循环依赖
依赖注入的四种方法
构造方法注入
javapublic HelloA(@Autowired HelloService helloService) { this.helloService = helloService; }
工厂方法注入
java@Bean(initMethod = "init", destroyMethod = "destory") public HelloB helloB(@Autowired HelloService helloService) { return new HelloB(helloService); }
set方法注入
java@Autowired public HelloC setHelloService(HelloService helloService) { this.helloService = helloService; return this; }
字段注入
java@Autowired HelloService helloService;
循环依赖的问题
其中构造方法注入和工厂方法注入是强依赖,因为Bean创建和属性注入放到一起了。
比如构造方法注入,创建对象的同时进行属性注入,这种属于强依赖。
而强依赖是解决不了循环依赖的问题的,因为创建对象和属性注入属于一体不可分的。
我们解决循环依赖是先创建对象,然后属性注入的时候利用三级缓存解决的。
循环依赖分析
在创建 Bean 的过程中,有依赖注入的过程。
这个阶段会发生循环依赖。
ABean创建–>依赖了B属性–>触发BBean创建—>B依赖了A属性—>需要ABean(但ABean还在创建过程中)
依赖场景
构造器注入循环依赖
java@Service public class A { public A(B b) { } } @Service public class B { public B(A a) { } } //A a = new A(new B(new A(new B(new A()))));
构造器的循环依赖Spring是无法解决的。
因为Spring解决循环依赖是在对象实例化之后,依赖注入时解决的。
构造器对应的是实例化阶段,无法解决循环依赖。
强依赖是无法解决循环依赖的问题的,因为Bean对象创建和属性注入是一个阶段。我们解决循环依赖是分为两个阶段,创建对象和属性注入,在属性注入时可以解决循环依赖。
依赖注入
利用三级缓存可以解决。
java@Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
Prototype类的循环依赖
无法解决,因为 Prototype作用域的类不会放到缓存中,Spring通过缓存解决循环依赖。
懒加载
懒加载的工作原理
当一个懒加载的 bean需要被初始化时,Spring会创建一个代理对象(通常是CGLIB 字节码动态代理)。
这个代理对象可以在其它类需要的时候,通过代理对象调用实际对象的方法。
而实际Bean在第一次使用的时候才会创建。
使用方式
@Lazy
三级缓存
Spring引入三级缓存解决依赖注入和set方法注入可能导致的循环依赖
singletonObjects
单例池,缓存的是已经经历了完整生命周期的Bean对象。
已经实例化并且已经属性赋值。
earlySingletonObjects
二级缓存,缓存的是早期的 Bean 对象,Bean的生命周期还没走完就放入了二级缓存。
已经实例化但是还未属性赋值。可以存放原对象,也可能存放代理对象。
singletonFactories
三级缓存,缓存的是ObjectFactory,表示对象工厂,用来创建早期 Bean对象的工厂,可以通过该工厂创建出代理对象。
为什么引入二级缓存
在循环依赖场景 A→B→A 中,实例化之后将A对象放到二级缓存中,这样 B 需要引用 A 时,从二级缓存获取就可以了。
Bean 实例化之后,整个 Bean生命周期 Bean对象的堆内存地址都不会改变。所以 B依赖的A(缓存)和走完生命周期的 A是一个对象。
为什么引入三级缓存
为了解决 AOP对象引入三级缓存。
Bean创建的生命周期可以简化为 实例化 → 依赖注入 → AOP。
AOP之后 Bean 实际上生成了一个代理对象,该代理对象和二级缓存里面存放的对象不是一个对象。这样就会导致 B 依赖的是 A的原始对象,但是需要的是 A的代理对象。
三级缓存的内容
三级缓存,缓存的是ObjectFactory,其实就是 一个函数式接口。
() -> getEarlyBeanReference(beanName, mbd,bean)
作用是能通过该函数获取一个对象的代理对象。
- 如果对象有 AOP,返回的就是 AOP 之后的代理对象。
- 如果对象没有 AOP,返回原对象。
注意:调用该函数不是在放入三级缓存的时候。
三级缓存的执行流程
- A 实例化。
- 将 A 对应的
ObjectFactory
放入三级缓存中。 - 依赖 B → 创建B → 实例化 B → 依赖 A。
- 从三级缓存获取 A 对应的 ObjectFactory。
- 通过 ObjectFactory 获取代理对象 A+(如果没有AOP,A+就是原对象)。
- 将代理对象 A+ 放到二级缓存,并且移除三级缓存中对应的
ObjectFactory
。 - B 读取二级缓存中的 A+,完成依赖注入。
- 清除二级缓存中的 B,然后将 B 放到一级缓存。
- B 注入完成之后,A完成依赖注入。
- 继续 A 的生命周期。
- 判断 A 是否需要 AOP。
- 如果需要 AOP,从二级缓存(earlySingletonObjects)中获取对象 A+,放入一级缓存。
- 如果不需要 AOP,将 A 放入一级缓存,并从二级缓存移除 A。