Spring源码分析——解决循环依赖

Spring源码分析——解决循环依赖

文章目录

  • Spring源码分析——解决循环依赖
    • 一、循环依赖
    • 二、循环依赖解决
      • 1.三级缓存
      • 2.循环依赖解决过程
        • (1)缓存添加过程
        • (2)获取缓存过程
      • 3.三级缓存解决循环依赖流程
    • 三、总结
    • 四、相关博客

一、循环依赖

什么是循环依赖?

存在类A实例Bean、类B实例Bean、类C实例Bean,在类A中引用了类B,类B中引用了类C,同时类C中引用了类A,此时使用Spring初始化类A、B、C便会存在循环引用的情况。当实例化A时发现其引用了B实例;于时对B进行实例化,实例化B时又对C实例进行引用;于时再实例化C,实例化C时,又引用了A实例,而此时A还在等待B初始化完成,因此出现死循环的现象。

Spring源码分析——解决循环依赖_第1张图片

二、循环依赖解决

1.三级缓存

在Spring中解决循环引用的方式是使用三级缓存策略,分别如下

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  1. 一级缓存:singletonObjects,用于缓存单例对象实例,这里存放的是已经实例完的对象Bean
  2. 二级缓存:earlySingletonObjects,用于存放早期实例的单例对象,这里存放的是正在创建实例,且还没实例完成的Bean
  3. 三级缓存:singletonFactories,用于缓存单例工厂

2.循环依赖解决过程

(1)缓存添加过程

单例模式下,第一次获取Bean时由于Bean示例还未实例化,因此会先创建Bean然后放入缓存中,以后再次调用获取Bean方法将直接从缓存中获取,不再重新创建。Spring中,获取Bean的流程:

Spring源码分析——解决循环依赖_第2张图片

缓存添加过程主要发生在创建Bean也就是doCreateBean()过程中。doCreateBean()中有这样一句代码

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

其源码如下

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		if (!this.singletonObjects.containsKey(beanName)) {
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

创建Bean主要经历了一下三个过程:

  1. createBeanInstance:通过构造方法创建实例
  2. populateBean:填充属性
  3. initializeBean:调用init()方法初始化Bean

从代码中可以看到在这里将beanName放入三级缓存中,并且从二级缓存中移除。这里的addSingletonFactory发生在createBeanInstance之后,此时已经有了实例对象,但是还没创建完成便放入三级缓存当中。在doCreateBean()方法执行完成之后,Bean实例创建完成,因此在第二个getSingleton()中的finally块中如果是新的单例对象则会调用addSingleton()方法。

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

可以看到此时将加入一级缓存中,并且从二级、三级缓存中移除。

(2)获取缓存过程

缓存的获取是通过getSingleton(String beanName)方法获取的,其源码如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

在代码中,首先从一级缓存singletonObjects中获取Bean;如果获取不到并且获取的Bean被标记为正在创建中,则从二级缓存earlySingletonObjects中获取;如果二级缓存中依然获取不到Bean,并且允许从三级换从中获取Bean,则从三级缓存中获取Bean,此时如果获取到了Bean,则将该Bean从三级缓存中移除,然后添加进二级缓存(缓存升级),否则返回null。

Spring源码分析——解决循环依赖_第3张图片

3.三级缓存解决循环依赖流程

通过使用三级缓存可以解决循环依赖的问题,以A、B、C相互依赖的情况为例,其解决循环依赖流程如下:

  1. 创建A对象,执行createInstance之后将对象放入三级缓存singletonFactories中提前曝光自己,并继续执行
  2. 执行populateBean为A对象填充属性,此时发现A依赖了B对象,于是通过getBean获取B对象,此时B还没创建,便会执行创建B的Bean对象流程
  3. 执行createInstance创建B对象,并放入三级缓存singletonFactories提前曝光
  4. 执行populateBean为B对象填充属性时,发现B对象依赖了C对象,于是getBean获取C对象
  5. 此时C对象未创建,于是执行创建C对象Bean过程,在填充C时发现C依赖A对象,于是通过getBean获取对象A
  6. 此时A对象并未初始化完成,仅仅是放入三级缓存中,在getSingleton时从三级缓存中取出A的ObjecFactory来获取对象,并且将A对象进行缓存升级,从二级缓存放入三级缓存,此时C也拿到了A对象实例
  7. A缓存从三级升到了二级,在Bean创建完成之后会通过addSingleton将缓存从二级再升到三级中。B、C流程相同

Spring源码分析——解决循环依赖_第4张图片

三、总结

  1. 当两个Bean相互引用依赖的情况称为循环引用,Spring使用三级缓存的方式解决循环依赖
  2. 循环依赖发生在创建Bean的过程中,具体发生在doCreateBean方法中调用createBeanInstance和populateBean方法时
  3. 由于在createBeanInstance时用构造方法提前曝光实例,之后再将实例放入三级缓存中,因此Spring不能解决构造函数中的循环依赖问题
  4. 在使用getSingleton获取Bean实例时,如果对象在三级缓存中,则会从三级缓存移除添加到二级缓存;如果在二级缓存中,则会从二级缓存中移除添加到一级缓存中,以达到缓存升级的目的
  5. 在三个级别缓存中,二级缓存和三级缓存从代码上看是互斥的,实例对象不能同时存在于二三级缓存当中

四、相关博客

Spring源码分析——Bean创建
Spring源码分析——获取Bean
Spring源码分析——解决循环依赖

你可能感兴趣的:(Spring,Java)