SpringIOC循环依赖底层原理

参考

什么是循环依赖

简单来说,就是A对象依赖了B对象,B对象依赖了A对象。

	// A依赖了B
	class A{
    
    
		public B b;
	} 

	// B依赖了A
	class B{
    
    
		public A a;
	}

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。

	A a = new A();
	B b = new B();

	a.b = b;
	b.a = a;

这样A,B就互相依赖了

但是在 Spring 中循环依赖就是一个问题了,因为,在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的 Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。当然,在 Spring 中,出现循环依赖的场景很多,有的场景 Spring 自动帮我们解决了,而有的场景则需要程序员来解决。

要明白Spring中的循环依赖,得先明白Spring中Bean的生命周期。

IOC容器加载过程

相当于所有单例Bean的创建

  1. 当new 一个 applicationContext时,执行容器加载,会从配置中(< bean > @Component @Bean)读取定义信息,加载成一个个的BeanDefination对象,存放在一个BeanDefinationMap<String(BeanName),BeanDefination>中
  2. 在BeanFactory(负责创建Bean,简单工厂设计模式)中循环创建所有BD,通过getBean方法生产Bean,创建步骤如下
    1. 首先去Bean容器(一级缓存 Map)中找是否已经存在Bean对象,找到则直接返回
    2. 没有找到则开始创建Bean:
      1. 实例化:拿到当前BD对象,bd.getBeanClass().newInstance()得到实例对象(反射)
      2. 属性注入DI(@Autowired):对象属性上有@Autowired注解的,会递归调用创建过程,这也是为什么会产生循环依赖的原因
      3. 初始化:回调初始化方法,自定义就实现InitializingBean接口,实现afterPropertiesSet()方法
    3. 最后将创建完成的Bean放入一级缓存(一个Map)singletonObjects中,并返回

循环依赖的解决

因为spring在进行属性注入的时候是通过递归调用getBean方法,所以可能会产生循环依赖问题,解决思路如下:

  1. 在实例化结束后,就将Bean对象直接加入到一级缓存singletonObjects中,但是这个对象是不完整的,多个线程访问可能会拿到不完整的Bean
  2. 为此,我们可以给一级缓存上锁,当多个线程来访问的时候,会等待当前线程生成完整的Bean对象,再访问,但是有些Bean本身就是完整的,线程也拿不到,效率很明显就下降了
  3. 为此,我们引入二级缓存earlySingletonObjects,把不完整的对象放入二级缓存,并给二级缓存上锁,完整的对象都放在一级缓存,所以效率大大提升;(二级缓存是为了解决循环依赖,并且能够解决并发下获取完整bena的性能问题)
  4. 但是如果两个线程,当前线程执行到一级缓存获取,发现没有,另一个线程生成了完整的bean并且将该bean放入一级缓存中,这时候当前线程执行到了二级缓存获取,发现没有,即认为两级缓存都没有,会选择创建,这很明显不应该,所以我们需要双重检查锁,即再检查一次一级缓存是否有该bean
  5. 正常来说,创建一个对象之后进行AOP动态代理(JDK、CGLIB实现),Spring选择在属性赋值后,初始化时进行AOP动态代理,为此又产生了新的问题:A依赖B,B依赖A的时候,B依赖的A的对象是不完整的,不是动态代理对象。
  6. 因此,普通的Bean必须在初始化的时候进行动态代理,而产生循环依赖的Bean,必须在属性赋值的时候生成动态代理对象放进二级缓存,并在最后从二级缓存拿出这个动态代理对象放进一级缓存
  7. Spring要求严格遵守Bean的生命周期,如果所有Bean都在属性赋值后生成动态代理对象,则违反了生命周期。因此我们需要判断是否产生循环依赖,即A对象需要产生第二次,也就是二级缓存里有A对象,我们就产生动态代理对象并将其放入二级缓存中
  8. 但是又产生新的问题,当A被B和C分别循环依赖,则产生两个动态代理对象;为了保证只创建一次动态代理对象,我们引入三级缓存singletonFactories,即第一次产生动态代理对象会放入三级缓存中,下次先检查三级缓存是否有动态代理对象,有则返回

总结:
一级缓存:单例池,里面有所有完整的Bean
二级缓存:解决循环依赖的单例性以及性能问题
三级缓存:为了保证动态代理只创建一次,解决循环依赖,是一个函数接口 解耦,职责单一

猜你喜欢

转载自blog.csdn.net/upset_poor/article/details/124059009