面试必问: spring 怎么解决循环依赖?

445 阅读7分钟

1、什么是循环依赖

所谓循环依赖是指,在A注入了B,在B中注入了A。初始化A时需要先初始化B,初始化B又需要初始化A,从而出现的类似死锁的现象。

2、spring 如何解决

2.1、循环依赖示例

@Component
public class A {
	@Autowired
	public B b;
}

@Service("b")
public class B {
	@Autowired
	public A a;
}

springIOC初始化过程中,我们了解到非懒加载的单例Bean都是在AbstractApplicationContext.refresh()方法中调用finishBeanFactoryInitialization()方法,最终通过AbstractBeanFactory.doGetBean()方法进行初始化的。那么spring如何解决循环依赖也必将在这个方法下面[spring 5.2版本源码]。

2.2、spring_bean生成时序图

在阅读源码前,先看看bean生成的时序图有助于理解源码。

  1. spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于spring容器中还没有A对象[getSingleton(A) = null],因此spring会创建A对象【spring创建一个bean分为三步:1. 实例化bean、 2. 给bean注入属性、3. 初始化bean】。并将A对象的半成品【未注入属性,未初始化】保存在三级缓存中[addSingletonFactory(A)]。
  2. 然后为A对象注入属性B,通过getBean(B)从spring容器中尝试获取B对象,由于spring容器还没有B对象,会创建B对象。
    • 创建 B 的半成品对象,并保存在三级缓存中[addSingletonFactory(B)]。
    • 然后为B对象注入属性A,通过getBean(A)从三级缓存中获取A的半成品对象的引用,将A从三级缓存移入二级缓存。并将它做为属性注入B对象
    • 初始化B对象后,返回B对象。将B对象保存到一级缓存中
  3. 最后将返回的B对象做为属性注入A对象,初始化A,并将A对象保存在一级缓存中。

2.3、源码解读

非懒加载的单例Spring_Bean最终都通过AbstractBeanFactory.doGetBean()进行初始化的。我们的源码分析也从这一个方法开始.

忽略其中与本次关联不大的代码

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	String beanName = transformedBeanName(name);
	Object bean;
	// 检查是否目标bean是否已经注册过.
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
	}else {
		// 单例
		if (mbd.isSingleton()) {
			// 将获取到的bean 加入一级缓存
			sharedInstance = getSingleton(beanName, () -> {
				try {
					// 创建 bean对象 
					return createBean(beanName, mbd, args);
				}catch (BeansException ex) {
				}
			});
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
	}
	return (T) bean;
}

其中值得关注的方法有三个:

  1. getSingleton(beanName);
  2. getSingleton(beanName,ObjectFactory);
  3. createBean(beanName, mbd, args); 接下来我们详细看一下这三个方法源码。

2.3.1、getSingleton(bean)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 查询一级缓存
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 查询二级缓存
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						// 查询三级缓存
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						// 如果查询到将查询到的数据写入二级缓存,然后从三级缓存中移除
						if (singletonFactory != null) {
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}
  1. 该方法分别从一级、二级、三级缓存中尝试获取bean,如果能获取到直接返回该对象,否则返回null。
  2. isSingletonCurrentlyInCreation方法用于判断目标bean是否处于正在创建阶段,如果是返回true。
  3. 如果入参allowEarlyReference为true,才会尝试查询三级缓存。且在三级缓存中查询到之后会将bean对象保存在二级缓存。

2.3.2、createBean(beanName, mbd, args)

getSingleton(beanName,ObjectFactory)方法中的第二个参数由createBean(beanName, mbd, args)提供,所以我们先分析createBean方法。

createBean(beanName, mbd, args)委托给同类的doCreateBean(beanName, mbdToUse, args)方法实现。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// BeanWrapper 是bean的包装类 它提供了 getPropertyDescriptors 方法 获取bean的属性
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	// 创建bean实例的包装类
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// earlySingletonExposure 1. 是单例。2. bean允许循环依赖。3. 该单例bean正在被创建
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
			logger.trace("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		// 加入三级缓存   -  getEarlyBeanReference 方法 默认返回入参中的bean对象
		// 此时的bean未注入属性,也就是@Autowried等注解还没有被解析
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	Object exposedObject = bean;
	try {
		// 注入属性,@Autowired 等在此处解析
		populateBean(beanName, mbd, instanceWrapper);
		// bean初始化
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {}
	// 去掉部分相对不重要代码
    return exposedObject;
}

spring_bean生成的三大步骤:

  1. 实例化:createBeanInstance(beanName, mbd, args)
  2. 属性注入:populateBean(beanName, mbd, instanceWrapper);
  3. 初始化:initializeBean(beanName, exposedObject, mbd);

在实例化和属性注入之间有一行关键性代码:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference(beanName, mbd, bean)方法最终会委托AbstractAutoProxyCreator.wrapIfNecessary()实现动态代理返回一个bean对象的代理类。

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
	this.advisedBeans.put(cacheKey, Boolean.TRUE);
	Object proxy = createProxy(
	bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
	this.proxyTypes.put(cacheKey, proxy.getClass());
	return proxy;
}

然后通过addSingletonFactory()将该代理类加入到三级缓存。

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);
		}
	}
}

populateBean(beanName, mbd, instanceWrapper)

该方法会解析bean类中需要注入的属性,如果需要注入的是一个bean对象,spring通过反射获取对应的bean,最终还是调用getBean()方法。

由于本次例子中是通过@Autowired注解进行属性注入,@Autowired其实是通过AutowiredAnnotationBeanPostProcessor后置处理器进行属性注入的。

我们来看看属性是如何注入

// 获取需要注入的 被@Autowired修饰的属性
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
	// 从metadata中获取需要注入的bean_name,然后最终通过getBean()方法进行获取
	metadata.inject(bean, beanName, pvs);
}

最终调用descriptor.resolveCandidate(autowiredBeanName, type, this);获取需要注入的bean

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
		throws BeansException {
	return beanFactory.getBean(beanName);
}

这边调用链路有点长,此处就不做赘述。有兴趣的可以根据以下链路自行查看

  • AbstractAutowireCapableBeanFactory.populateBean()
  • AutowiredAnnotationBeanPostProcessor.postProcessProperties()
  • AutowiredAnnotationBeanPostProcessor.inject()
  • DefaultListableBeanFactory.resolveDependency()
  • DefaultListableBeanFactory.doResolveDependency()
  • DependencyDescriptor.resolveCandidate()

总结

查看完以上源码,我们再次回顾一下本次的面试题《spring 怎么解决循环依赖?》

spring容器中存在三级缓存【分别是singletonObjects、earlySingletonObjects、singletonFactories】,spring在实例化bean之后,会将bean对象的引用添加到三级缓存中,在循环依赖发生时,spring会将正在初始化过程中的不完全bean的引用先作为属性注入。最终将bean对象初始化后添加到一级缓存【singletonObjects】中。

假设存在A 和 B 存在循环依赖,此时先将A注册到spring容器中。

  1. 先尝试在spring的三级缓存中获取bean对象,如果获取不到,创建一个新的bean A
  2. 先实例化A,然后将不完全的A对象的引用,保存到三级缓存中。
  3. 再对A进行属性B的注入,发现B对象还没有再spring容器中,创建B
    • 实例化B,然后将不完全的B对象的引用,保存在三级缓存中。
    • 然后对B进行属性A的注入,通过查询发现A对象存在于三级缓存中,将A对象保存到二级缓存中。然后注入B中。【B对象中注入的是A对象的引用】
    • 初始化B之后,此时对象B是一个完整的bean,将它保存在一级缓存中。
  4. B创建成功后,返回一个B对象的引用。并将其注入A。
  5. A进行初始化【A初始化完成后,A也是一个完整的bean,那么B中A属性引用对象也完整了】,然后保存到一级缓存中。

虽然存在循环依赖,但是在构造器注入的情况下,循环依赖仍然会报错。

根据源码我们得知setter注入和注解注入,是在populateBean方法上进行的递归(getBean),此时三级缓存已经保存,所以循环依赖不会出错。

但是构造器注入的递归操作发生在createBeanInstance(beanName, mbd, args)这个方法中。此时三级缓存还没有保存,在缓存中获取失败时,又会重新create新的bean。这也就出现了循环依赖。

有兴趣的小伙伴可以跟一下源码。

代码链路:

  • createBeanInstance(beanName, mbd, args)
  • AbstractAutowireCapableBeanFactory.autowireConstructor()
  • ConstructorResolver.autowireConstructor()
  • ConstructorResolver.resolveConstructorArguments()
  • BeanDefinitionValueResolver.resolveValueIfNecessary();
  • BeanDefinitionValueResolver.resolveReference()
    • 最终在这个方法中调用了getBean()方法