Spring 源码分析-循环依赖

概述

在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过案例介绍一下 Bean 的循环依赖过程和怎样解决。

setter方式单例

我们分别创建两个类,A B 它们之间相互依赖,如下代码

A 类

@Component
public class A {

    private B b;

    public A( B b){
        this.b = b;

    }

    public void getB(){
        System.out.println(b);
    }

}

B 类

@Component
public class B {

    private A a;

    public B(A a){
        this.a = a;

    }

    public void getA(){
        System.out.println(a);
    }


}

SpringCircleConfig 类

@Configuration
@ComponentScan("com.zlp.spring.circle.set")
public class SpringCircleConfig {
 
 
}

测试类

public class AnnotationSetCircleTest {

    public static void main(String[] args) {
        
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
        A a = ac.getBean("a",A.class);
        System.out.println(a.toString());
        a.getB();
    }
}

首先回忆一下之前介绍的 Spring 初始化过程的内容,在 Spring 初始化 Bean 的过程中,有一个重要很重要的步骤就是属性赋值。这个属性赋值的过程简单来讲就是获取每一个Bean对象内部的属性,然后通过反射把属性值赋给这个Bean对象。对于基本类型的值,例如String、Integer等,很简单,直接解析赋值即可。

对于对象类型的值,例如上述示例中,它的处理过程是什么呢?

第一步:A对象初始化完毕,此时开始进行属性填充,发现 A中需要注入B,则按照创建Bean的流程去创建B

第二步:在创建 B 的过程中,首先去初始化 B,然后进行属性填充,发现 B 中需要注入 A,则按照创建Bean的流程在去创建A;

按照上述的过程会发现创建带有依赖关系的 Bean 的过程就会变成一个死循环,一直转圈。但是上述的示例可以正常运行,这是因为 Spring 中通过引入了早期对象以及使用多级缓存解决了这个问题。

整体的工作流程如下

早期对象

什么是早期对象呢?

早期对象指的是 Bean 的一个半成品。更确切的说,就是实例化完成(没有初始化),并执行了一部分后置处理器,但是还未填充属性的对象,这个对象不能直接使用。例如对于上例中的 A 对应的早期对象其实就是一个空的 A 实例,如下:

如何使用这个早期对象?

这个早期对象 A 创建出来之后,会被保存在一个地方【Spring的二级缓存中】。在进行 B 实例的属性赋值过程中,发现依赖了A这个对象,然后就会去找A对象。没找到初始化完成的A对象会再去找这个早期的A对象,找到之后,直接完成B实例的属性赋值,将这个没有初始化完成的 A 对象赋值给B中的 a 属性,此时,B完成属性填充。

然后 A 中依赖了 B,A 属性填充过程中同样会将未初始化完成的B对象填充给 b 属性,至此,完成依赖Bean的属性填充。

使用半成品的对象进行属性填充会有问题吗?

不会有问题。在属性填充过程完成之后,会进行 bean 的初始化过程,初始化过程中,初始化的 bean对象就是这个半成品对象,因为属性赋值的过程其实就是把对象的引用赋值给依赖Bean的成员变量【反应在内存中就是同一个地址引用】。所以当这个对象被初始化之后,之前已经完成赋值的半成品对象自然就会变成初始化完成的对象。

多级缓存

底层代码是通过三级缓存实现,对应三个 Map,代码过于繁多,具体可参考:

DefaultSingletonBeanRegistry.java 类中

/**
	 * Cache of singleton objects: bean name to bean instance.
	 * 一级缓存:单例对象的缓存,也被称作单例缓存池
	 */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
	/**
	 * Cache of early singleton objects: bean name to bean instance.
	 * 二级缓存:提前曝光的单例对象的缓存,用于检测循环引用
	 */
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
	/**
	 * Cache of singleton factories: bean name to ObjectFactory.
	 * 三级缓存:保存对单实例bean的包装对象
	 */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/**
	 * 按照注册顺序所保存的已经注册的单例对象的名称
	 */
	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);



	/**
	 * Add the given singleton object to the singleton cache of this factory.
	 * <p>To be called for eager registration of singletons.
	 * @param beanName the name of the bean
	 * @param singletonObject the singleton object
	 */
	protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			// 将创建好的单实例bean放入到单例缓存池中
			this.singletonObjects.put(beanName, singletonObject);

			// 从三级缓存中删除
			this.singletonFactories.remove(beanName);

			// 从二级缓存中删除(早期对象:已经实例化,但是未完成属性赋值的对象)
			this.earlySingletonObjects.remove(beanName);

			// 保存到已注册单实例Bean名称集合中
			this.registeredSingletons.add(beanName);
		}
	}

	/**
	 * 添加三级环境
	 * <p>To be called for eager registration of singletons, e.g. to be able to
	 * resolve circular references.
	 * @param beanName the name of the bean
	 * @param singletonFactory the factory for the singleton object
	 */
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			// 如果当前的单实例缓存池中还没有beanName对应的单实例bean
			if (!this.singletonObjects.containsKey(beanName)) {
				// 将当前beanName对应的ObjectFactory放入到三级缓存singletonFactories中
				this.singletonFactories.put(beanName, singletonFactory);

				// 从早期的单例对象缓存中移除beanName对应的bean实例
				this.earlySingletonObjects.remove(beanName);

				// 将当前的beanName保存到已经注册的bean对应的Set集合中,标识其已经注册过
				this.registeredSingletons.add(beanName);
			}
		}
	}

	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		// 从缓存中获取单实例Bean
		return getSingleton(beanName, true);
	}

	/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	
	//   ******该段代码是 Spring 解决循环引用的核心代码******
	//
	//   解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),
	//      并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存),
	//      通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,
	//      从而避免出现循环引用导致的死循环。
	//
	//    这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,
	//      该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。


	//	另外这个代码块中引进了4个重要缓存:
	//		singletonObjects 缓存:beanName -> 单例 bean 对象。
	//		earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。
	//		singletonFactories 缓存:beanName -> ObjectFactory。
	//		singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。
	//		singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。
	
	/**
	 * 注意:
	 * (1)通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。
	 * (2)构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。
	 * (3)非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 根据beanName从单实例对象缓存中获取单例对象(singletonObjects为一个ConcurrentHashMap,就是用来保存所有的单实例Bean的,
		//   key:beanName value:beanInstance) 相当于一级缓存
		Object singletonObject = this.singletonObjects.get(beanName);
		// 如果缓存中不存在,而且beanName对应的单实例Bean正在创建中.
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 加锁操作.
			synchronized (this.singletonObjects) {
				// 从早期单实例对象缓存中获取单例对象(之所以称为单实例早期对象,
				//   是因为earlySingletonObjects里面的对象都是通过提前曝光的ObjectFactory创建出来的,还未进行属性的填充)
				singletonObject = this.earlySingletonObjects.get(beanName);


				// 如果早期单实例对象缓存中没有,而且允许创建早期单实例对象引用
				if (singletonObject == null && allowEarlyReference) {
					// 则从单例工厂缓存中获取BeanName对应的单例工厂.
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						// 如果存在着单例对象工厂,则通过工厂创建一个单例对象,
						// 调用的是:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))中的拉姆达表达式
						singletonObject = singletonFactory.getObject();
						// 将通过单例对象工厂创建的单例对象放入到早期单例对象缓存中,这个早期对象指的是一个空的未完成属性赋值和初始化的对象。
						this.earlySingletonObjects.put(beanName, singletonObject);
						// 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放入到earlySingletonObjects缓存中了,
						//  所以,后续通过beanName获取单例对象,可以通过earlySingletonObjects缓存获取到,不需要再用到该单例工厂.
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

	/**
	 * Return the (raw) singleton object registered under the given name,
	 * creating and registering a new one if none registered yet.
	 * @param beanName the name of the bean
	 * @param singletonFactory the ObjectFactory to lazily create the singleton
	 * with, if necessary
	 * @return the registered singleton object
	 */
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		// 校验bean的名称,不能为空
		Assert.notNull(beanName, "Bean name must not be null");

		synchronized (this.singletonObjects) {

			// 首先从单例缓存池singletonObjects【Map<String,Object>】中尝试获取单实例bean
			Object singletonObject = this.singletonObjects.get(beanName);

			// 如果未获取到,则通过如下的过程去创建单实例bean
			if (singletonObject == null) {
				/** 如果当前bean工厂中的实例bean正在被销毁,则不允许执行bean的创建过程 */
				if (this.singletonsCurrentlyInDestruction) {
					throw new BeanCreationNotAllowedException(beanName,
							"Singleton bean creation not allowed while singletons of this factory are in destruction " +
							"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
				}

				/** 创建单实例bean之前的检查,根据名称校验当前的单实例bean是否正在创建中. */
				beforeSingletonCreation(beanName);

				boolean newSingleton = false;
				/** 初始化用来保存异常信息的Set集合 */
				boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
				if (recordSuppressedExceptions) {
					this.suppressedExceptions = new LinkedHashSet<>();
				}
				try {
					/** 回调ObjectFactory的getObject方法,进行单实例Bean的创建. */
					singletonObject = singletonFactory.getObject();
					/** 标注单实例bean创建成功 */
					newSingleton = true;
				}
				catch (IllegalStateException ex) {
					// Has the singleton object implicitly appeared in the meantime ->
					// if yes, proceed with it since the exception indicates that state.
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
				catch (BeanCreationException ex) {
					if (recordSuppressedExceptions) {
						for (Exception suppressedException : this.suppressedExceptions) {
							ex.addRelatedCause(suppressedException);
						}
					}
					throw ex;
				}
				finally {
					if (recordSuppressedExceptions) {
						this.suppressedExceptions = null;
					}
					/** 创建完成之后,将bean名称从检查列表中删除. */
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					/** 如果bean创建成功,将其加入单实例bean的map中 */
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}



}

具体调用方法 AbstractBeanFactory.doGetBean方法中的 getSingleton(beanName)方法。

三级缓存对象

  1. singletonObjects:一级缓存,也被称为单实例缓存池,用来缓存所有初始化完成的Bean;
  2. earlySingletonObjects:二级缓存,用来保存早期对象,这个早期对象就是Bean的一个半成品对象,只完成了实例化化,未进行属性填充和初始化的对象;
  3. singletonFactories:三级缓存,用来保存获取早期对象的一个回调函数,通过这个回调函数,可以获取到未初始化完成的早期对象;

查询流程

  • 根据Bean的名称首先查询一级缓存,查询到直接返回;
  • 未查询到单实例Bean而且允许使用早期对象,则查询二级缓存,查询到则直接返回;
  • 二级缓存中未查询到早期对象,则通过回调获取早期对象的方法获取早期对象,然后保存到二级缓存中并返回。

获取早期对象的实现可参考:

AbstractAutowireCapableBeanFactory.doCreateBean 中的 getEarlyBeanReference 方法。

获取 bean 调用流程方法


构造器参数循环依赖

构造器循环依赖

通过使用早期对象以及多级缓存解决了这种通过set方法注入时存在的循环依赖问题。但是通过构造器注入的方式无法解决,因为通过构造器的方式无法产生早期对象。如下示例:

A类

@Component
public class A {

    private B b;

    public A(@Lazy B b){
        this.b = b;

    }

    public void getB(){
        System.out.println(b);
    }


}

B类

@Component
public class B {

    private A a;

    public B(@Lazy A a){
        this.a = a;

    }
    public void getA(){
        System.out.println(a);
    }


}

测试类不变,运行之后,会提示如下异常:

BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方式

构造器方式注入产生的循环依赖,可以通过指定 Bean 的延迟加载,先注入代理对象,然后在需要使用这个bean的时候再去真实创建,如下:

A类

@Component
public class A {

    private B b;

    public A(@Lazy B b){
        this.b = b;

    }


    public void getB(){
        System.out.println(b);
    }


}

B类

@Component
public class B {

    private A a;

    public B(@Lazy A a){
        this.a = a;

    }


    public void getA(){
        System.out.println(a);
    }


}

通过其他例如 @PostConstruct,或者使用后置处理器的方式也可以解决,但是如果真有这种循环依赖问题,建议还是使用最简单,也最容易理解的set方式去注入,因为这种方式框架本身就已经解决了这种问题。

setter方式原型,prototype

我们在A类和B类中,添加 @Scope(value = "prototype") 注解

@Component
@Scope(value = "prototype")
public class A {

    @Autowired
    private B b;


    public void getB(){
        System.out.println(b);
    }


}

scope="prototype" 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的bean都使用Prototype作用域,无状态的一般都使用singleton单例作用域。

测试从新启动报错

Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a';
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

为什么原型模式就报错了呢 ?

对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

几个 QA?

三级缓存放流程

QA 问题

1、三级缓存存对象,在获取数据的时候什么顺序来获取的

先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存中存在了对象

级缓存。

2、一级能解决循环依赖问题

有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以需要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放半成品。

3、如果只有二个缓存,能否解决循环依赖问题?

在刚刚的整个流程中,三级缓存一共出现了几次? getsingleton, doCreateBean

如果对象的创建过程中不包含aop,那么二级缓存就可以解决循环依赖问题,但是包含aop的操作,

循环依赖问题是解决不了的。

4、为什么添加了aop的操作之后就需要添加三级缓存来解决这个问题?三级缓存加了什么操作?

添加了一个 getEarlyBeanReference 的方法在创建代理对象的时候,

是否需要生成原始对象?

需要当创建完成原始对象之后,后续有霃要创建代理对象,那么对象在引用的时候应该使用哪一个

换句话说,就是一个 beanName对应有两个对象,(原始对象和代理对象在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原

程序是怎么知道在什么时候要进行代理对象的创建的呢?

需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去

创建代理对象, getEarlyBeanReference 方法的判断,如果需要代理就返回代理对象,如果没有代

理就返回原始对象。


参数文档

  1. Spring循环依赖的三种方式
  2. Spring如何解决bean的循环依赖?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值