Spring之循环依赖源码解析

目录

1.什么是循环依赖?

2.为什么会出现循环依赖?

3.面对循环依赖问题,我们该如何思考解决?

4.Spring是怎么解决循环依赖的?

5.总结


1.什么是循环依赖?

有两个类Order、Customer,Order对象依赖了Customer对象,同时Customer对象也依赖了Order对象,这就构成了循环依赖;

// Order依赖了Customer 
public class Order{
  private Customer customer;
}

// Customer依赖了Order
public class Customer{
  private Order order;
}

如果我们自己去创建对象,不考虑Spring的话,对象之间相互依赖也并没有什么问题;但是,在Spring中循环依赖就是一个问题了, 在Spring中,一个对象并不是简单的new出来了,而是会经过一系列的Bean的生命周期,就是因为有Bean的生命周期,所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员自己来解决。

2.为什么会出现循环依赖?

Spring之循环依赖源码解析_第1张图片

在Spring中,从实例化Bean对象到初始化完成,大致要经历三步(事实上Spring创建Bean有很多步骤),依赖注入就是在属性赋值这一步中完成的,实例化bean之后,Spring需要给对象中的属性进行赋值(依赖注入),那么Spring中循环依赖的问题是怎么出现的呢?

@Component
public class ServiceA{
  @Autowired
  private ServiceB b; // ServiceA依赖了ServiceB
}


@Component
public class ServiceB{
  @Autowired
  private ServiceA a; // ServiceB依赖了ServiceA
}

Spring之循环依赖源码解析_第2张图片

比如上面的ServiceA类,ServiceA类中存在一个ServiceB类的属性,属性名为b,当ServiceA类创建出来一个bean实例之后,就要给b属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取ServiceB类所对应的bean,如果此时BeanFactory中存在ServiceB类对应的bean,就直接获取并赋值给b属性,如果此时BeanFactory中不存在ServiceB对应的bean,则需创建一个ServiceB对应的bean实例,然后赋值给b属性;这个过程会存在一个问题:如果此时ServiceB类在BeanFactory中还没有生成对应的bean,则会触发ServiceB类bean的创建,就会经过ServiceB创建bean的生命周期, 在创建ServiceB的bean的过程中,如果此时ServiceB中也存在一个ServiceA类的a属性,那么在创建ServiceB的bean的过程中就需要ServiceA类对应的bean,但是,触发ServiceB类bean创建的条件是ServiceA类bean在创建过程中的依赖注入,所以,这里就出现了循环依赖,这就是循环依赖的场景;在Spring中,通过某些机制可以帮助开发者解决部分循环依赖的问题,这个机制就是三级缓存。

3.面对循环依赖问题,我们该如何思考解决?

Spring之循环依赖源码解析_第3张图片

3.1. 在ServiceA、ServiceB之间增加缓存来打破循环

想要打破ServiceA和ServiceB之间的这种循环状态,那就不能让双方在创建bean实例时都触发对方bean的创建,否则循环依赖问题依然得不到解决;那么怎么做呢?可以通过增加缓存来实现,具体做法如下:

Spring之循环依赖源码解析_第4张图片

ServiceA创建bean的过程中,在进行依赖注入之前,先把实例化出来的ServiceA的原始对象(这里的原始对象是指没有经过属性赋值,或只经过了部分属性赋值的bean)放入缓存(提前暴露,只要放到缓存中,其它bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,而ServiceA依赖了ServiceB,所以,接下来要给ServiceA找一个ServiceB类型的bean,赋值给ServiceA的b属性;如果ServiceB的bean不存在,则会触发ServiceB的bean的创建,经过ServiceB创建bean的生命周期,而创建ServiceB的bean的过程和ServiceA一样,也是先创建一个ServiceB的原始对象,然后把ServiceB的原始对象提前暴露出来放入缓存中,接着再对ServicrB的原始对象进行依赖注入,而ServicrB也依赖了ServiceA,此时能从缓存中拿到ServiceA的原始对象(这里只是ServiceA的原始对象,还不是经过完整生命周期的bean)赋值给ServiceB的a属性,ServiceB就可以正常的完成依赖注入,这样ServiceA和ServiceB都能完成正常bean的创建,就不会陷入循环中。

问题:ServiceB在进行依赖注入时,赋值给ServiceB属性的是ServiceA的原始对象,并不是经过完整生命周期的对象,这样赋值正确吗?

答:在整个bean创建过程中,都只有一个ServiceA对象,所以对于ServiceB而言,即使在属性注入时,注入的是ServiceA原始对象,也没有影响,因为这里赋的是ServiceA在内存中的地址值,ServiceA原始对象在后续的生命周期流程中在堆中的没有发生变化,当ServiceA经过了完整生命周期后,前面赋值给ServiceB属性的ServiceA对象,也就变成了具备完整生命周期的对象。

3.2.要解决循环依赖,还需要考虑AOP
1) 基于上面的场景,不知道有没有发现一个问题?

从缓存中取出ServiceA的原始对象注入给ServiceB的a属性之后,待ServiceB完成创建后,回到ServiceA创建bean的流程中,在ServiceA后面的生命周期步骤中,当执行到初始化后这一步,ServiceA的原始对象进行了AOP,生成了一个代理对象,对于ServiceA来说,最终生成的bean对象应该是进行了AOP之后的代理对象,而赋值给ServiceB的a属性的bean对象,不是ServiceA进行AOP之后的代理对象,而是原始对象;所以,如果仅仅是基于上面<3.1. 在ServiceA、ServiceB之间增加缓存来打破循环>来解决的话,就会出现这样的问题;

2) 怎么解决ServiceB依赖的ServiceA对象和最终的ServiceA对象不是同一个对象?

要解决这个问题,就得提前进行AOP,如果要解决循环依赖,但依然是在初始化后进行AOP,那么赋值给ServiceB的a属性的那个对象就不是代理对象;按照正常的bean的生命周期流程,AOP是在初始化后进行的,但我们要考虑循环依赖的场景,就需要提前进行AOP,当然,这与正常的创建bean的生命周期流程不符,因为,正常bean的创建是在初始化后这一步进行AOP的,所以,也只有在出现循环依赖的情况下(例如ServiceA出现了循环依赖),才需要提前进行AOP,最后将进行过AOP步骤的对象缓存起来;

3.3.增加三级缓存

基于<3.2.要解决循环依赖,还需要考虑AOP>,还存在这样的问题:

1)出现循环依赖是怎么判断的?

增加集合来记录(Spring中是singletonsCurrentlyInCreation);当ServiceA正在创建时,将该bean对应的beanName存到集合中,当ServiceB创建需要用到ServiceA时,但发现ServiceA仍在创建中,那么就可以断定ServiceA出现了循环依赖;

2)怎么判断出现了循环依赖的bean是否要进行AOP?

基于BeanPostProcessor机制,BeanPostProcessor会根据传入的bean去判断是否要进行AOP,如果需要进行AOP就生成代理对象并返回,否则就把传入的原始bean对象返回;在这里就要增加三级缓存了,增加一个Map,key是beanName,value是判断是否要进行AOP的Lambda表达式;

4.Spring是怎么解决循环依赖的?

4.1.Spring 通过三级缓存解决了循环依赖

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

/** Cache of singleton factories: bean name to ObjectFactory. */
//三级缓存
private final Map> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
//二级缓存
private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);

一级缓存 : Map singletonObjects,单例池,用于保存实例化、属性赋值(依赖注入)、初始化完成的 bean实例(经过完整的生命周期的bean对象);

二级缓存 : Map earlySingletonObjects,早期"曝光"对象,用于保存实例化完成的bean实例(暂时还没有经过完整的生命周期的bean对象);

三级缓存 : Map> singletonFactories,早期"曝光"对象工厂,用于保存 bean 创建工厂,以便于后面扩展有可能创建代理对象,二级缓存中存储的就是从这个工厂中获取到的对象。

Spring之循环依赖源码解析_第5张图片

 4.2.Spring解决循环依赖源码分析

1) 向三级缓存添加

在创建bean的过程中,当执行createBeanInstance()方法创建完原始的bean对象,之后调用populateBean方法进行属性赋值(依赖注入),但是在属性赋值之前,Spring会判断当前正在创建的bean是否支持循环依赖,如果支持,就会添加到三级缓存(singletonFactories),singletonFactories中存的是某个beanName对应的ObjectFactory,这个ObjectFactory 是一个函数式接口,所以支持Lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean),就是一个ObjectFactory,执行该Lambda表达式就会去执行getEarlyBeanReference方法,注意这里只会往三级缓存添加,并不会执行该Lambda表达式,具体调用过程是在后面调用ObjectFactory的getObject方法时调用,具体源码如下:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
//如果当前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");
    }
  // 为了解决循环依赖提前缓存单例创建工厂
  // 循环依赖-添加到三级缓存
  // 执行到这里的时候,Spring并不知道当前正在创建的bean会不会出现循环依赖,先缓存起来,目的是后面 
  // 判断出现循环依赖的时候使用
   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);
		}
	}
}

2)Lambda表达式中的getEarlyBeanReference方法分析

getEarlyBeanReference方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法, 在整个Spring中,默认只有AbstractAutoProxyCreator类实现了SmartInstantiationAwareBeanPostProcessor接口中getEarlyBeanReference方法,而该类就是用来进行AOP的,AOP是通过一个BeanPostProcessor来实现的,开启AOP之后,Spring容器中就会增加一个BeanPostProcessor,这个BeanPostProcessor就是 AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在 Spring中AOP利用的是JDK的动态代理,或者是CGLib的动态代理,如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象;

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
	    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

Spring之循环依赖源码解析_第6张图片

3) 什么时候执行Lambda表达式,并执行getEarlyBeanReference方法?

 当判断出现循环依赖时,执行ObjectFactory的getObject()方法,就会执行getEarlyBeanReference方法,循环依赖是通过getSingleton方法中的isSingletonCurrentlyInCreation()来判断的;

/**
 * Return the (raw) singleton object registered under the given name.
 * 

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 */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock 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; }

4) AbstractAutoProxyCreator的getEarlyBeanReference方法到底做了些什么?

首先得到一个cachekey,cachekey就是beanName,然后把beanName和bean(这是原始对象)存入earlyProxyReferences中,调用 wrapIfNecessary方法, wrapIfNecessary方法是用来进行AOP的,wrapIfNecessary方法中会判断当前bean是否要进行AOP,如果要则要生成一个代理对象,否则还是返回原始对象,而Spring用于解决循环依赖的getEarlyBeanReference方法和Bean的生命周期初始化后的postProcessAfterInitialization方法中都会调用wrapIfNecessary方法;

// AbstractAutoProxyCreator
// 提前进行AOP
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   //记录当前的bean进行了AOP
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}
//正常进行AOP的地方
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
        //判断当前bean有没有提前进行AOP
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

说明:只有出现了循环依赖,Spring才需要将AOP提前,才会执行getEarlyBeanReference方法,调用wrapIfNecessary方法,但是,不管有没有出现循环依赖都会执行初始化后的postProcessAfterInitialization方法,进而调用其中的wrapIfNecessary方法;earlyProxyReferences的作用是在Spring出现了循环依赖的情况下,记录提前进行了AOP的bean,避免在进行初始化后这一步时,重复进行AOP,生成新的代理对象(wrapIfNecessary方法会判断当前bean是否要进行AOP,如果需要则生成代理对象,如果不需要还是返回原始对象,所以上述所说的提前进行AOP,最终返回的对象并不一定是代理对象)。

5) 如果提前进行AOP,在初始化后这一步就不会再进行AOP,返回的对象就不是代理对象而是原始对象,由于最终要把代理对象放到单例池,这个对象是从哪里获取的?

从二级缓存中获取

 //说明:当某个正在创建的bean出现了循环依赖,同时该bean也要进行AOP,那么就会提前进行AOP,一但提前进行了AOP,就会生成代理对象,
		//那么在初始化后这一步就不会再进行AOP,返回的对象就不是代理对象而是原始对象,由于最终要把代理对象放到单例池,所以,这里还要去二级缓存去获取
		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
					String[] dependentBeans = getDependentBeans(beanName);
					Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					if (!actualDependentBeans.isEmpty()) {
						throw new BeanCurrentlyInCreationException(beanName,
								"Bean with name '" + beanName + "' has been injected into other beans [" +
								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
								"] in its raw version as part of a circular reference, but has eventually been " +
								"wrapped. This means that said other beans do not use the final version of the " +
								"bean. This is often the result of over-eager type matching - consider using " +
								"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
					}
				}
			}
		}
对于上面 exposedObject == bean 的判断,大多数情况下是相同的,但是也有某些场景会不同,比如Spring的异步调用,此时List beanPostProcessors中会增加一个AsyncAnnotationBeanPostProcessor,经过AsyncAnnotationBeanPostProcessor处理的bean就会生成新的代理对象;

Spring之循环依赖源码解析_第7张图片

4.3.为什么要有二级缓存和三级缓存?

只有二级缓存确实可以解决循环依赖的问题,但有一个前提:这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用二级缓存(这里的二级缓存是指singletonFactories,缓存的是Lambda表达式)是无法解决问题,这样会导致每次执行singleFactory.getObject()方法都产生一个新的代理对象,无法保证对象的单例性,所以还要借助另外一个缓存来保存产生的代理对象;如果出现循环依赖+AOP时,多个地方注入这个动态代理对象需要保证都是同一个对象,而三级缓存中的取出来的动态代理对象每次都是新对象,地址值不一样。

三级缓存的作用是为了解决Spring中Bean在进行依赖注入时发生的循环依赖。如果不需要AOP,那么只需要二级缓存即可实现,如果有AOP,其实二级缓存也能够实现,但是会打破Bean的生命周期,这不符合Spring的设计原则,由于需要把AOP对象放入二级缓存中,那么就必须在所有需要AOP处理的Bean对象初始化之前就对Bean对象进行后置处理(生成AOP对象),即使没有出现循环依赖,这并不是Spring想看到的,所以Spring引入了三级缓存,而且存入的是结构,ObjectFactory是一个Lambda表达式,相当于一个回调函数,当出现循环依赖的时候,会执行Lambda表达式,获取到Bean对象或者 AOP代理对象,再将Bean对象或者 AOP代理对象存入二级缓存中,如果之后还有循环依赖指向该对象(类似 A 依赖 B , B 依赖 A , C 依赖 A这种情况),就直接从二级缓存里面获取,从而解决了循环依赖。(为什么不直接在二级缓存里存放Lambda表达式?因为同一个Lambda表达式每执行一次,就会生成一个新的代理对象,不能保证单例)。

4.4.Spring有没有解决singleton下的构造注入产生的循环依赖?

@Component
public class ServiceA {

  private ServiceB serviceB;

  public ServiceA(ServiceB serviceB) {
	this.serviceB = serviceB;
  }

  public void test() {
	System.out.println(serviceB);
  }

}


@Component
public class ServiceB {

  private ServiceA serviceA;

  public ServiceB(ServiceA serviceA) {
	this.serviceA = serviceA;
  }

  public void test() {
	System.out.println(serviceA);
  }

}


基于构造注入的方式下产生的循环依赖也是无法解决的,原因是ServiceA通过构造方法创建对象时,无法创建ServiceA所依赖的ServiceB对象,获取所依赖bean对象的引用。
创建bean对象,执行顺序为:1.调用构造方法  2. 放到三级缓存  3. 属性赋值

调用构造方法时会触发所依赖的bean对象的创建,createBeanInstance是调用构造方法创建bean对象,在里面会注入构造方法中所依赖的bean,而此时并没有执行到addSingletonFactory方法来添加主bean对象的创建工厂到三级缓存singletonFactories中。

4.5.Spring有没有解决多例下的set注入产生的循环依赖?
多实例并不会调用getSingleton方法,自然也不会缓存。也就是说,无论多少次创建多实例bean都不会调用beforeSingletonCreation;

注意:当两个bean的scope都是prototype的时候,才会出现异常,如果其中一个是singleton,就不会出现。

4.6.Spring解决循环依赖的机理

Spring为什么可以解决set + singleton模式下循环依赖?根本原因在于:该方式可以做到将"实例化Bean"和"给Bean属性赋值"这两个动作分开去完成,实例化Bean的时候:调用无参数构造方法来完成,此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界,给Bean属性赋值的时候,调用setter方法来完成。两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值,这样就解决了循环依赖的问题。

5.总结

总结一下三级缓存:

1. singletonObjects:缓存经过了完整生命周期的bean

2. earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要进行AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects,但是不管怎么样,即使是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects中的可以统一认为是未经过完整生命周期的bean。

3. singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean 的创建过程中,经过实例化得到一个原始对象后,都会提前基于原始对象构造一个Lambda表达式,并保存到三级缓存中,如果当前Bean没有出现循环依赖,那么这个Lambda表达式不会被执行,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖 (当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行 Lambda表达式得到一个对象,并把得到的对象放入二级缓存(如果当前Bean需要AOP,那么执行Lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象)。

你可能感兴趣的:(Spring全家桶,#Spring源码,spring,java)