面试题-spring如何解决循环依赖

前几天刷博客,看到一篇面试题,要求介绍spring解决循环依赖的原理。自信对spring源码比较了解,不过看到这个问题一时不知如何作答,于是我重新翻出来源码,一探究竟。

一、什么是循环依赖

循环依赖指的是多个bean之间互相引用,可以通过属性引用,也可以通过构造函数的参数引用,最终导致这些bean之间形成一个依赖环。如下图:
在这里插入图片描述

形成循环依赖方式有:

  1. 通过构造函数的入参引用;
  2. 通过属性引用,可以分为:单例bean的引用和prototype bean的引用。

二、循环依赖会导致启动失败吗

这一节通过代码来看一下不同方式下形成的循环依赖会导致什么结果。

1、构造函数依赖

下面的代码是A和B之间通过构造函数形成循环依赖。

@SpringBootApplication(scanBasePackageClasses=Test.class)
public class Test {
    public static void main(String[] args) throws Exception {
        ApplicationContext context =new SpringApplicationBuilder(Test.class)
                .web(WebApplicationType.NONE).bannerMode(Banner.Mode.OFF)
                .run(args);
    }
}
@Component("a")
class A{
    public A(@Qualifier("b")B b){}
}
@Component("b")
class B{
    public B(@Qualifier("a")A a){}
}

运行后,报出如下错误:

The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  a defined in file [C:\Users\XX\workspace\apps\app\target\classes\com\parser\util\A.class]
↑     ↓
|  b defined in file [C:\Users\XX\workspace\apps\app\target\classes\com\parser\util\B.class]
└─────┘
 - o.s.b.d.LoggingFailureAnalysisReporter

2、单例bean引用

A和B之间通过属性形成循环依赖。

@Component("a")
class A{
    @Autowired
    private B b;
}
@Component("b")
class B{
    @Autowired
    private A a;
}

程序正常启动,没有报错。

3、prototype bean引用

定义bean类型为prototype,通过属性形成循环依赖。

@Component("a")
@Scope(value="prototype")
class A{
    @Autowired
    private B b;
}
@Component("b")
@Scope(value="prototype")
class B{
    @Autowired
    private A a;
}

运行后报出如下错误:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'a' defined in com.Test: Unsatisfied dependency expressed through method 'getA' parameter 0; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in com.Test: 
Unsatisfied dependency expressed through method 'getB' parameter 0; 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?

错误信息提示有循环依赖。

4、结论

spring可以解决单例bean之间的循环依赖,对于其他类型的循环依赖,spring会报错,系统无法正常启动。

三、源码解析

1、构造函数依赖

下面以小节“1、构造函数依赖”的代码为例进行讲解。
spring启动时,检测到需要创建A对象,那么便调用getBean()方法获取对象,在创建对象之前会首先执行下面这个方法:

	protected void beforeSingletonCreation(String beanName) {
	    //singletonsCurrentlyInCreation是一个集合,表示对象正在创建中
	    //每创建完一个对象会执行与本方法相对的另一个方法:afterSingletonCreation
	    //afterSingletonCreation方法用于将已经创建完成的对象从singletonsCurrentlyInCreation集合中删除
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

执行beforeSingletonCreation()方法时,将“a”放入到集合singletonsCurrentlyInCreation中,接下来执行A的构造方法,发现需要B对象,那么接着调用getBean()方法获取B对象,同样的首先也是先执行beforeSingletonCreation()方法,将“b”放入到集合singletonsCurrentlyInCreation中,接下来发现B的构造方法又需要A对象,那么继续调用getBean()方法,此时执行beforeSingletonCreation方法时,发现singletonsCurrentlyInCreation集合中已经有了“a”,singletonsCurrentlyInCreation.add(beanName)返回false,beforeSingletonCreation()方法抛出异常表示有循环依赖。

2、prototype bean依赖

prototype bean是在通过getBean()方法获取对象时,才创建的。
下面以小节“3、prototype bean引用”的代码为例进行讲解。
系统启动后,通过getBean(“a”)获取对象时,spring检测出A对象是prototype,那么首先执行检测方法,检测是否发生循环依赖:

	protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		//prototypesCurrentlyInCreation是ThreadLocal对象
		//它保存了本次对象创建过程中创建的所有对象
		//第一次执行该方法,curVal为null,本方法返回false,表示没有循环依赖
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

isPrototypeCurrentlyInCreation()执行完后,接下来执行如下方法:

	//beforePrototypeCreation方法将每次创建的对象都保存到prototypesCurrentlyInCreation中,用于做循环检测
	//第一次执行该方法时curVal为null
	//第二次执行该方法时,curVal为上一次创建的对象名字
	//后续再次执行该方法时,prototypesCurrentlyInCreation里面保存的是set对象
	protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			//将bean的名字放入ThreadLocal对象中
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			//第二次执行本方法走该分支
			Set<String> beanNameSet = new HashSet<>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			//后续再次执行本方法走该分支
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}

执行完上述方法后,执行createBean()方法创建出A对象,然后执行populateBean(beanName, mbd, instanceWrapper)方法设置A对象中的属性b,那么spring默认需要获取名字为“b”的对象,接下来spring执行beanFactory.getBean(beanName)来获取“b”对象,与获取“a”对象类似,也是执行了上面这些方法。执行完这些方法之后创建出B对象,此时发现B对象的属性需要“a”对象,那么spring再次执行beanFactory.getBean()方法获取a对象,那么接下来首先进入检测方法isPrototypeCurrentlyInCreation():

	protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		//本次执行该方法是第三次执行,curVal是一个set对象,
		//里面保存了“a”和“b”,因此该方法返回true
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

第三次执行isPrototypeCurrentlyInCreation方法时返回true,表示存在循环依赖,接下来spring抛出循环依赖的异常信息,启动报错。

3、单例bean依赖

spring启动时发现需要创建单例a对象,那么便调用getBean()方法。getBean()方法又调用doCreateBean()执行A的构造方法创建出A对象,构造方法执行完后在doCreateBean()方法里面会执行一个非常关键的判断:

//earlySingletonExposure表示对象是否可以提前暴露,此时还没有设置A对象的属性,仅仅执行了构造方法
//allowCircularReferences表示是否允许循环依赖,默认是true,可以配置
//isSingletonCurrentlyInCreation()检测当前对象是否正在创建中,因为A对象的属性还没设置,属于正在创建中,返回true
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));

这里先来看一下方法isSingletonCurrentlyInCreation():

	public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

singletonsCurrentlyInCreation是不是很熟悉,介绍构造方法依赖中也使用了该属性,在创建对象之前都会先执行beforeSingletonCreation()方法,因此执行isSingletonCurrentlyInCreation()方法时,singletonsCurrentlyInCreation集合里面已经包含了“a”。
此时earlySingletonExposure是true,接下来执行方法addSingletonFactory():

//先执行下面的if,因为earlySingletonExposure是true,接着执行addSingletonFactory()
if (earlySingletonExposure) {
	if (logger.isTraceEnabled()) {
		logger.trace("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	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);
			}
		}
	}

addSingletonFactory()方法将字符串“a”添加到singletonFactories、registeredSingletons中,同时从earlySingletonObjects删除,这里还涉及到了singletonObjects,下面先说一下这四个属性的含义:

  • singletonFactories:用于解决循环依赖,存储当前对象的对象工厂ObjectFactory,通过对象工厂可以获得半成品的实例对象(也就是还未设置属性的对象),在循环依赖中,如果对象还未创建完成,便可以从ObjectFactory里面获得半成品对象,一旦获取半成品对象,就会从该集合中删除;
  • registeredSingletons:用于保存对象的名字;
  • earlySingletonObjects:用于解决循环依赖,从ObjectFactory里面得到半成品对象后,将该半成品对象放入到earlySingletonObjects集合中,这样以后就可以从该集合中直接得到半成品对象了,当对象完全创建完成后,会从该集合中删除;
  • singletonObjects:保存最后创建完毕的实例对象,这里的实例对象是已经设置好属性,执行了init-method方法,执行了Aware接口方法,也就是说一旦放入该集合中意味着不需要spring容器在对该对象做任何处理。

循环依赖就是依靠earlySingletonObjects、singletonFactories属性解决的。
执行完addSingletonFactory方法后,接下来bean后处理器AutowiredAnnotationBeanPostProcessor检查是否有需要注入的属性,此时发现需要注入对象“b”,那么便调用beanFactory.getBean(beanName)方法获得“b”对象,与a对象一样还是执行上述这些逻辑,之后后处理器AutowiredAnnotationBeanPostProcessor检查到需要注入“a”对象,那么接下来还是调用beanFactory.getBean(beanName)方法。该方法里面首先调用getSingleton()方法:

    //入参beanName=“a”,allowEarlyReference=true
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		//singletonObjects中没有a对象,且a对象还是创建当中
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				//在执行addSingletonFactory方法时,已经删除了a
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						//获得a对象
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

getSingleton()返回的便是还未设置属性的“a”对象,之后将“a”对象设置到“b”的属性中,这样“b”对象创建完毕,“a”对象也是一样的道理。
下图展示的是上述执行流程:
在这里插入图片描述

4、其他场景循环依赖

从上面的原理可以分析出下面这些场景的属性依赖:
1、当prototype bean依赖单例,单例又依赖prototype bean时,启动成功,因为spring容器启动时,不会主动创建prototype bean,只有在创建单例时,发现需要prototype bean,才会去创建,那么此时没有形成循环依赖;
2、当单例依赖prototype bean,prototype bean又依赖单例时,启动成功;
3、单例自身依赖自身,启动成功;
4、prototype bean自身依赖自身,启动失败。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值