一文彻底搞懂spring循环依赖

前言

在关于Spring的面试中,我们经常会被问到一个问题:Spring是如何解决循环依赖问题的?这个问题
算是关于Spring的一个高频面试题,如果不刻意研读,即使度过源码,面试者也不一定能够一下子回答得上。

本文主要针对这个问题,从源码角度对其实现原理进行剖析。

1.什么是循环依赖?

循环依赖其实就是对象之间的循环引用,即两个或两个以上的Bean互相持有对方,最终形成闭环。

在这里插入图片描述

用代码的形式演示,更容易理解,如下是ClassA和ClassB的声明:

public class ClassA {

	private ClassB classB;

	public ClassB getClassB() {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}

public class ClassB {

	private ClassA classA;

	public ClassA getClassA() {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}
2.循环依赖处理机制?
  • 单例bean构造器参数方式循环依赖(无法解决)

    首先先来演示通过构造器参数方式的循环依赖,结合源码来分析,为什么构造器参数的方式无法解决循环依赖。

    // 触发所有非延迟加载单例bean的初始化,主要步骤为getBean
    for (String beanName : beanNames) {
    	// 合并父BeanDefinition对象
    	// map.get(beanName)
    	RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    	if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    		if (isFactoryBean(beanName)) {
    			Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    			// 如果是FactoryBean则加&
    			if (bean instanceof FactoryBean) {
    				final FactoryBean<?> factory = (FactoryBean<?>) bean;
    				boolean isEagerInit;
    				if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    					isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
    									((SmartFactoryBean<?>) factory)::isEagerInit,
    							getAccessControlContext());
    				}
    				else {
    					isEagerInit = (factory instanceof SmartFactoryBean &&
    							((SmartFactoryBean<?>) factory).isEagerInit());
    				}
    				if (isEagerInit) {
    					getBean(beanName);
    				}
    			}
    		}
    		else {
    			// 实例化当前bean
    			getBean(beanName);
    		}
    	}
    }
    

在这里插入图片描述

进入getBean()方法

public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

再次进入到doGetBean()方法

在这里插入图片描述

首先会尝试从缓存中获取对象,此时还没有这个对象,接着往下看

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里会将每一个正在创建的BeanName放入singletonsCurrentlyInCreation集合中,该集合在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry中定义。

private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));

接着往下走,执行到singletonObject = singletonFactory.getObject(),会进入到前面的lamda表达式中的createBean方法中。

在这里插入图片描述

进入doCreateBean方法中,这里会有几个比较重要的几个部分,此步骤是将对象实例化。

在这里插入图片描述

接下将实例化的对象提前暴露到singletonFactories(也就是三级缓存)

在这里插入图片描述

进入addSingletonFactory方法里

在这里插入图片描述

接着往下执行,来到populateBean方法,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,就会递归的调用getBean()方法尝试获取目标bean

在这里插入图片描述

进入方法内部,执行到applyPropertyValues

在这里插入图片描述

进入applyPropertyValues方法内部

在这里插入图片描述
在这里插入图片描述

接下来就是重点了,进入方法内部,找到bean = this.beanFactory.getBean(refName)这行代码。

在这里插入图片描述

这里发现ClassA对象依赖于ClassB对象,又回到getBean()方法,但是要注意的是,此时getBean里的参数是classB,此时容器中还不存在ClassB这个对象,又回到创建ClassB的过程,执行到属性装配的步骤时,ClassB对象又依赖于ClassA对象,又回到之前的步骤,执行到getSingleton中的beforeSingletonCreation(beanName)方法时,发现此时ClassA已经存在singletonsCurrentlyInCreation这个集合中了,接着会抛出异常,整个过程结束。(这也是为什么构造器参数形式不支持循环依赖的原因)

Error creating bean with name 'classA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
  • prototype原型bean方式循环依赖(无法解决)

    在这里插入图片描述
    ClassA对象在创建之前,会进行标记这个bean正在被创建,等创建之后会将标记删除。

    进入beforePrototypeCreation方法,看一下此方法都做了哪些事。

在这里插入图片描述

这里可以看到,prototypesCurrentlyInCreation是一个ThreadLocal,ThreadLocal是什么呢?其实它就是一个线程局部变量,它的功能就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。也就是说,此处会将beanName存放到线程局部变量中,后面创建对象的时候,首先会查看这个局部变量是否有值,如果没有值,说明这个对象还没有进行创建,会进入到创建对象的步骤,如果检测到这个beanName已存在,就会抛出异常。这也是为什么prototype原型bean方式循环依赖无法解决的原因。

在这里插入图片描述

Error creating bean with name 'classA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?
  • singleton单例bean的setter方式循环依赖

    再来看一下singleton单例bean的settter方式的循环依赖,它是通过提前暴露一个ObjectFactory对象来完成的,简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中,ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在,Spring容器初始化ClassB,同时也将ClassB暴露到Spring容器中,ClassB调用setClassA方法,Spring从容器中获取ClassA,因为之前已经提前暴露了ClassA,因此可以获取到ClassA实例,ClassA通过给Spring容器获取到ClassB,完成对象初始化操作,这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。

3.Spring如何解决循环依赖问题?

Spring正是通过singleton单例bean的setter方式解决循环依赖的,ClassA在创建的时候,会将自己放入到singletonObjectFactories三级缓存中,在属性装配的时候,检测到ClassA对象依赖于ClassB对象,Spring首先尝试从容器中获取ClassB,此时ClassB不存在,Spring容器会初始化ClassB,同时也将ClassB暴露到容器中,接着就是对ClassB进行属性装配,Spring从容器中获取ClassA,因为之前已经将ClassA暴露到容器中了,因此可以获取到ClassA实例,并将ClassA升级放入二级缓存earlySingletonObjects中,ClassA通过给Spring容器获取到ClassB,完成了ClassB的初始化操作,这样ClassA和ClassB都完成了对象初始化操作,便解决了循环依赖问题。

ClassA升级放入earlySingletonObjects的代码

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	// 判断当前单例bean是否正在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 是否允许从singletonFactories中通过getObject拿到对象
			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;
}

ClassB初始化完成放入单例池的代码(此步骤是对ClassA进行属性装配时,发现ClassB不存在,将会对ClassB

进行创建,属性装配,对象初始化等一系列步骤执行完之后)

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
	// 将此bean(已成型的Spring Bean)移入一级缓存(单例池)
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

那么ClassB对象完成了ClassB的初始化操作,这样ClassA和ClassB都完成了对象初始化操作。ClassA形成最终的Spring Bean对象之后,会将其放入单例池中,代码同上。

到这里,Spring整个解决循环依赖问题的实现思路已经比较清楚了。对于整体过程,读者朋友只要理解两点:

  • Spring是通过递归的方式获取目标bean及其所依赖的bean的;

  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

也就是说,Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,它将数据和操作封装在对象中,通过对象之间的交互实现程序的设计和开发。下面是一些关键概念,帮助你更好地理解Python面向对象编程。 1. 类(Class):类是对象的蓝图或模板,描述了对象的属性和行为。它定义了对象的特征和方法。例如,我们可以定义一个名为"Car"的类来表示汽车,其中包含属性(如颜色、型号)和方法(如加速、刹车)。 2. 对象(Object):对象是类的实例,是具体的实体。通过实例化类,我们可以创建一个对象。例如,我们可以创建一个名为"my_car"的对象,它是基于"Car"类的实例。 3. 属性(Attribute):属性是对象的特征,用于描述对象的状态。每个对象都可以具有一组属性。例如,"Car"类的属性可以包括颜色、型号等。 4. 方法(Method):方法是对象的行为,用于定义对象的操作。每个对象都可以具有一组方法。例如,"Car"类的方法可以包括加速、刹车等。 5. 继承(Inheritance):继承是一种机制,允许我们创建一个新类(称为子类),从现有类(称为父类)继承属性和方法。子类可以扩展或修改父类的功能。继承可以实现代码重用和层次化设计。 6. 多态(Polymorphism):多态是一种特性,允许不同类的对象对同一方法做出不同的响应。多态提高了代码的灵活性和可扩展性。 7. 封装(Encapsulation):封装是一种将数据和操作封装在对象中的机制,隐藏了对象的内部实现细节,只暴露必要的接口给外部使用。这样可以保护数据的安全性,提供了更好的模块化和代码复用性。 通过理解这些概念,你可以更好地掌握Python面向对象编程。在实践中,你可以使用类来创建对象,操作对象的属性和调用对象的方法,通过继承和多态实现代码的灵活性和可扩展性,通过封装保护数据的安全性和提高代码的可维护性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值