目录
1.什么是循环依赖?
2.为什么会出现循环依赖?
3.面对循环依赖问题,我们该如何思考解决?
4.Spring是怎么解决循环依赖的?
5.总结
有两个类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自动帮我们解决了,而有的场景则需要程序员自己来解决。
在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
}
比如上面的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.1. 在ServiceA、ServiceB之间增加缓存来打破循环
想要打破ServiceA和ServiceB之间的这种循环状态,那就不能让双方在创建bean实例时都触发对方bean的创建,否则循环依赖问题依然得不到解决;那么怎么做呢?可以通过增加缓存来实现,具体做法如下:
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.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
二级缓存 : Map
三级缓存 : Map
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;
}
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的异步调用,此时ListbeanPostProcessors中会增加一个AsyncAnnotationBeanPostProcessor,经过AsyncAnnotationBeanPostProcessor处理的bean就会生成新的代理对象;
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方法给属性赋值,这样就解决了循环依赖的问题。
总结一下三级缓存:
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,则直接得到一个原始对象)。