基于最新Spring 5.x,从源码的角度详细介绍了Spring的循环依赖产生的原因,并且提供有效的解决办法。包括各种循环依赖,以及“三级缓存”。
Spring 循环依赖是面试中的一个热点问题。 如果我们想要更加透彻彻的了解它的原理,那么我们必须详细掌握Spring容器初始化以及Spring AOP等等知识点的流程和源码,看懂这些需要花费大量的时间和精力,而我们在此前已经讲过了IoC和AOP的源码,因此这篇文章中的某些方法的源码在此不会赘述。
如果想要初步了解Spring循环依赖的原理,我们必须知道最基本的Spring的bean初始化流程,下面是普通non-lazy-init
的单例Bean进行创建是的关键步骤时序图,这些步骤很重要,直接关系到后面的循环依赖执行流程!
关于这张图,没有看过源码的小伙伴,肯定有很多疑惑的地方,因此不必太过纠结,可以跳过。
Spring可以自动帮助我们解决setter方法和基于注解的字段注解反射的循环依赖注入,当然这要求互相依赖的两个bean不能都是prototype原型的。
假设两个类如下:
@Component
public class ClassA {
@Resource
private ClassB classB;
}
//--------------------
@Component
public class ClassB {
@Resource
private ClassA classA;
}
ClassA和ClassB都是最普通的对象,也没有代理,它们互相持有对方的引用,通过@resource注解(或者@Autowired注解)注入,这就是项目中最常见的循环引用,这两个类可能就是不同的Service实现。
下面我们通过Spring的源码结合上面的时序图来看看它是如解决这种循环引用,Spring源码基于5.2.8.RELEASE!
首先,DefaultListableBeanFactory#preInstantiateSingletons
方法中,Spring尝试创建classA,它会走到getBean(beanName)
方法:
getBean(beanName)
方法固定调用AbstractBeanFactory#doGetBean
方法:
继续跟进,来到了关键的doGetBean方法,该方法的源码在IoC容器初始化系列文章中有详细讲解!
进入doGetBean方法后,首先就会尝试调用DefaultSingletonBeanRegistry#getSingleton(beanName)
方法直接从缓存中获取ClassA的实例。该方法内部调用另一个两个参数的getSingleton方法
,allowEarlyReference参数为true,即允许获取早期bean实例,也就是说,有可能会从singletonObjects、earlySingletonObjects、singletonFactories
这三个缓存中获取实例!
当然,我们这里设定ClassA被首先初始化,那么肯定缓存中是没有的,因此会获取null。因此将会走else的逻辑,也就是创建bean实例。
随后会校验,当前bean是否是prototype作用域,并且是否正在创建中
,如果是,那么将抛出BeanCurrentlyInCreationException
异常,这就是我们上面说的:“互相依赖的两个bean不能都是prototype”的原理!
然后就是判断如果当前BeanFactory中没有该beanName的bean定义,并且存在父BeanFactory,那么从父BeanFactory中查找实例并返回,对于一般的项目是没有父子容器的概念的,但是SSM项目则可能同时存在Spring 容器和MVC容器,MVC容器是子容器。
然后就是判断如果不是为了类型检查,那么将会调用markBeanAsCreated(beanName)
方法将当前beanName
加入到alreadyCreated
缓存集合中,该集合将会在后面校验循环依赖的时候起作用!通过getBean(beanName)
方法进来的逻辑在这里的都会将beanName加入到alreadyCreated缓存集合中。
然后就是获取当前bean定义依赖的bean,保证它们首先被创建,这里的“依赖”是指的depends-on
属性或者@DependsOn
注解指定的beanName,而不是依赖的字段属性。
然后就到了创建bean实例的时候了,可以看到会调用DefaultSingletonBeanRegistry#getSingleton
方法,该方法的第二个参数是一个lambda对象,也就是说ObjectFactory#getObject
方法最终是调用的createBean
方法。
我们进入getSingleton方法,首先就是在加锁之后再次尝试从singletonObjects缓存中获取bean实例,这里如果再获取不到,那么就真正的走创建对象的逻辑了。
在创建单例bean之前(singletonFactory.getObject()
方法之前),会调用beforeSingletonCreation
方法,用于将对应的beanName存入缓存singletonsCurrentlyInCreation集合中,表示正在创建该beanName的单例实例。如果添加成功,那么正常返回;如果添加失败,那说明此前已经有了该beanName缓存,可能是出现了构造器的循环依赖,即同一个bean实例被创建多次,Spring不允许构造器循环依赖,此时抛出异常。
随后就执行singletonFactory.getObject()
方法获取实例:
也就是调用AbstractAutowireCapableBeanFactory#createBean
方法,最终会走到AbstractAutowireCapableBeanFactory#doCreateBean
方法。
首先会调用createBeanInstance
方法选择合适的构造器创建bean实例,这里仅仅会创建实例,可能进行了构造器的依赖注入,但是并没有进行setter方法和注解字段反射属性注入。
createBeanInstance
方法的源码非常非常的复杂,但是我们在此前已经详细讲解过了,方法完成之后,将会保证创建了对应的bean实例,但是属性可能没有填充。
随后执行applyMergedBeanDefinitionPostProcessors
方法,这个方法将会查找全部MergedBeanDefinitionPostProcessor
类型的后处理器,回调它们的postProcessMergedBeanDefinition
扩展点方法,该方法可以改变已合并的bean定义,当然还能做更多的事情,最常见作用的就是解析字段和方法上注解,比如CommonAnnotationBeanPostProcessor会解析@WebServiceRef、@EJB、@Resource,AutowiredAnnotationBeanPostProcessor会解析@Autowired、@Value、@Inject注解等等。注意,在这一步仅仅是简单解析这些注解,相当于查找并缓存起来,后面某些时候,比如依赖注入的时候会被再次调用,到时候将进一步深度解析它们的配置属性和功能。
applyMergedBeanDefinitionPostProcessors方法的源码非常非常的复杂,但是我们在此前已经详细讲解过了
,方法完成之后,将会保证解析了各种注解,执行了各种扩展操作。
实际上,上面的流程对于循环依赖似乎还没有太大联系,但是下面的步骤将会非常重要!
接下来将会判断如果当前bean是singleton单例Bean,并且允许循环依赖(也就是所谓的allowCircularReferences
属性,默认为true,即允许),并且正在创建当前单例bean实例(也就是此前调用beforeSingletonCreation
方法时加入的singletonsCurrentlyInCreation
缓存集合中存在该beanName)。那么设置earlySingletonExposure
变量为true,表示允许早期单例对象暴露,可被用于创建代理对象,进而可能变更早期单例对象实例。
随后判断如果允许暴露早期单例,那么那么创建一个ObjectFactory的lambda对象
通过addSingletonFactory
方法存入singletonFactories
缓存中,这一步非常重要,用于解决setter方法和注解反射注入的单例bean循环依赖以及AOP代理对象的创建的问题。
请注意,这里的第二个参数同样是一个lambda对象,这里ObjectFactory的getObject
实际上是调用getEarlyBeanReference
方法。这里的getEarlyBeanReference同样很重要,可以解决基于AbstractAutoProxyCreator创建的AOP代理对象之间的循环依赖,比如普通的自定义Spring AOP代理以及Spring声明式事务的代理。但是Spring异步任务则是通过AbstractAdvisingBeanPostProcessor来实现代理的,因此可能出现循环依赖异常,这一点我们后面会介绍。
接下来将exposedObject
赋值为最原始的bean实例!随后开始初始化bean实例,分为两步。第一执行的方法是populateBean
,简单的说就是进行setter方法和注解字段反射的属性注入,因此有可能由于依赖其他bean而导致其他bean的先被初始化。
populateBean方法是循环依赖的转折点,在这里,将会解析到ClassA中的ClassB依赖,然后转而初始化ClassB。
populateBean
方法的源码非常非常的复杂,但是我们在此前已经详细讲解过了,方法完成之后,将会保证解析了各种注解,执行了各种扩展操作。在解析到某个依赖之后,最终仍然将会最终通过getBean方法来获取这个依赖的实例,比如解析@Resource注解并通过beanName查找依赖的时候(这也是案例中的依赖查找逻辑),将会调用AbstractAutowireCapableBeanFactoryresolveBeanByName
方法,而该方法中将会调用getBean
方法:
这个getBean方法是不是很熟悉,还记得我们初始化ClassA的时候吗,getBean
这个方法就是入口呀!只不过这里的name参数变成了的“classB”,并且指定了类型:
这样,ClassB也执行了getBean方法,那么我们就从走一遍上述的流程就行了,此前的ClassA经历的流程ClassB也会经历,包括创建ClassB的实例。走啊走啊,我们又来到了populateBean
方法,只不过这一次的主角换成了ClassB。
该方法会为ClassB注入依赖,ClassB依赖的是谁呢?这不正是ClassA吗?继续往下走,发现又到了resolveBeanByName
方法:
咦,这次不就真正的又回到了起点了吗,就连beanName都是“classA”了,那么,这次重走getBean方法获取“classA”实例和第一次走getBean方法获取“classA”实例有什么区别吗?
还记得刚进入doGetBean
方法的逻辑吗?好像是首先就调用getSingleton(beanName)
尝试从缓存中去对应的bean实例,当然这次也不例外:
这和第一次有什么区别吗?当然有啦,在第一次创建了ClassA实例之后,随后会判断如果允许暴露早期单例,那么那么创建一个ObjectFactory的lambda对象通过addSingletonFactory方法存入singletonFactories缓存中,这一步非常重要,用于解决setter方法和注解反射注入的单例bean循环依赖以及AOP代理对象的创建的问题。(前面走过这个步骤)。
那么现在,在singletonFactories
缓存中就存在“classA”对应的ObjectFactory实例。自然这一次就可以直接从缓存中获取ClassA的实例而不再需要从新创建:
注意,该实例通过singletonFactory.getObject()
方法获取,而该参数是一个lambda对象,因此实际上会调用getEarlyBeanReference
方法:
如果没有AOP代理,那么这个getEarlyBeanReference
方法将返回原始对象,如果有AOP代理,那么将返回一个代理对象,后面我们会专门讲解,这里默认没有代理!
在获取到singletonObject
之后,注意,会把结果存入earlySingletonObjects
缓存,然后从singletonFactories
缓存中移除,实际上一个beanName对应的实例只能同时存在与“三级缓存”中的某一个缓存中,因此,所谓的“三级缓存”是不准确的,我并不想使用这个说法!
getSingleton(beanName)
执行完毕,我们已经从缓存中获取了ClassA的实例,为什么叫“早期bean”呢,因为此时获取的ClassA还可能没有初始化完毕,它的populateBean
方法以及后续方法还没有执行,可能还没有填充完毕属性。
有人会问,这里获取的是不完整的对象,那么对于ClassB中依赖的ClassA属性也是不完整的对象,那么有什么影响吗?当然,普通情况下是没有影响,因为我们只需要保证我们使用的ClassA实例和注入到其他地方的ClassA实例是同一个对象即可,那么后续在进行了ClassA的后续初始化和填充属性的时候,因为它们保存了同一个对象地址,其他类中的ClassA依赖也能使用到这些填充的属性!当然,如果存在代理的行为,那么这就不一样了,这个我们放到后面再介绍!
getSingleton(beanName)
执行完毕,获取的结果不为null,因此将进入if
的逻辑,else的逻辑不会进入,自然也不会创建ClassA的实例,这样也保证了“单例”的逻辑!
剩下的就是判断并转换类型并返回结果了!
这样,在ClassB装配过程中就获取到了早期的ClassA的实例,它和此前创建的ClassA对象是同一个对象,这得益于getSingleton方法和singletonFactories缓存的设置!
获取到了之后,会将结果通过字段反射注入到对应的ClassB中的classA属性中,最终所有的依赖查找、注入完毕之后,ClassB的populateBean方法将会返回。但此时,最开始的ClassA的populateBean方法还卡着呢,因为此时ClassB的doCreateBean方法还需要向下执行!
随后就是执行initializeBean方法, initializeBean用于初始化bean实例,比如执行各种bean初始化方法、@PostConstruct 方法、Aware接口、以及回调BeanPostProcessor后处理器的各种方法,比如postProcessBeforeInitialization方法和postProcessAfterInitialization方法。如果没有代理,那么将返回原始的同一个对象。
随后判断如果earlySingletonExposure为true
,即允许获取早期单例对象,那么进行一系列用于注入的属性以及循环依赖的正确性的校验,这是非常重要的一步。
可以看到,刚进来就是一个getSingleton(beanName, false)方法,这个方法的beanName就是当前的“classB”,第二个参数为false,什么意思呢?也就是不会从singletonFactories这个缓存中查找“classB”,我们知道,ClassB的实例也存入了singletonFactories缓存,但是其他的两个缓存中没有,因此该方法将会返回null。
如果earlySingletonReference为null,那么后面的下面的逻辑也不会走了。
随后,将尝试为ClassB的实例注册bean销毁回调,在容器被销毁时会进行销毁回调,比如@PreDestroy
方法。
最终,将返回创建的ClassB实例,这里的ClassB实例是一个完整的实例!
创建ClassB的getBean方法返回之后,将获取到了完整的ClassB实例,其内部注入从缓存中获取的早期的(不完整的)ClassA实例。
当ClassA的其他依赖装配完毕之后,ClassA的populateBean方法执行完毕,终于,ClassA可以继续向后执行了!
向下就是initializeBean方法了,这里initializeBean用于初始化bean实例,比如执行各种bean初始化方法、@PostConstruct 方法、Aware接口、以及回调BeanPostProcessor后处理器的各种方法,比如postProcessBeforeInitialization方法和postProcessAfterInitialization方法。如果没有代理,那么将返回原始bean实例,如果存在代理,比如aop包中的AbstractAutoProxyCreator和AbstractAdvisingBeanPostProcessor这两个后处理器,将会在postProcessAfterInitialization方法中可能基于原始对象创建代理对象,最终导致返回的exposedObject是一个代理对象,与原始的bean实例不是同一个对象,这样就可能出现问题,这就是我们后面会了解的知识。
随后判断如果earlySingletonExposure
为true,即允许获取早期单例对象,那么进行一系列用于注入的属性以及循环依赖的正确性的校验,这是非常重要的一步。
可以看到,刚进来就是一个getSingleton(beanName, false)方法,这个方法的beanName就是当前的“classA”,第二个参数为false,什么意思呢?也就是不会从singletonFactories这个缓存中查找“classA”,只会从singletonObjects和earlySingletonObjects中查找。
但是我们知道,ClassB在填充ClassA属性的时候,在从singletonFactories缓存中获取早期ClassA实例之后,会将结果存入earlySingletonObjects缓存中,因此,我们最终将会获得一个ClassA的实例,这一点和ClassB在执行到此时的结果是有区别的,也就是不为null。
因此,这将会继续if里面的逻辑!
判断如果exposedObject和bean一致,也就是说经过了initializeBean返回的实例和最开始获取的实例是“同一个对象”,那么说明,在初始化ClassA的时候没有进行AOP代理(如果进行了代理,一定会创建一个新的代理对象),因此将exposedObject置为earlySingletonReference,也就是从缓存中获取的并且应用了getEarlyBeanReference方法的bean实例,早期的ClassA实在方法中可能进行了代理也可能没有,因此最终需要返回earlySingletonReference这个对象。
剩下的就是注册销毁回调,返回最终结果的逻辑,返回结果就是earlySingletonReference,如果没有代理,那么实际上就是此前创建的同一个ClassA实例。
doCreateBean
方法执行完毕, DefaultSingletonBeanRegistry#createBean
方法也执行完毕,那么getSingleton
方法中的singletonFactory.getObject()
方法会执行完毕,此时回到getSingleton方法继续进行后续逻辑(ClassB初始化的时候也有这一段逻辑,但是没说出来)。
在创建单例bean之后(singletonFactory.getObject()方法之后),会调用afterSingletonCreation
方法,用于将对应的beanName从缓存singletonsCurrentlyInCreation集合中移除,表示创建该beanName的单例实例完毕,无论有没有创建成功。
最后,如果成功的获取到了Bean实例,那么还会将最终的实例存入singletonObjects
缓存中,并从其他缓存singletonFactories、earlySingletonObjects
中移除,表示Bean最终初始化完毕。
此后就是doGetBean方法中的getSingleton方法返回,然后调用getObjectForBeanInstance方法,主要处理FactoryBean的逻辑,主要就是根据name是否带有“&”前缀,来判断是想要获取普通bean或者FactoryBean内部的自定义bean或者FactoryBean本身。
随后会尝试转换为类型(如果设置了类型并且不一致),最终返回转换后的结果,doGetBean方法结束,getBean方法也结束!
最终,我们回到了起点preInstantiateSingletons方法。
第二次获取ClassB的时候,则直接通过getSingleton方法从缓存中获取ClassB的实例,获取的缓存是singletonObjects。
这样,循环依赖解决完毕,还是很简单的!
我们以更加简单的文字来总结一下,ClassA和和ClassB,假设首先获取ClassA:
可以看到,避免普通循环依赖无限创建实例的关键就是那几个缓存集合!对于普通bean的setter方法和字段注解注入的循环依赖,Spring可以帮我们自动解决。
如果某个Bean需要进行代理操作,那么最终我们需要的是一个代理对象,而不是原始bean实例,这样的情况,Spring能帮我们自动解决吗?结论就是:默认情况下,通过特定的方式创建的代理对象可以帮忙解决,而通过某些方式创建的代理对象则无法解决。
Spring默认可以解决基于AbstractAutoProxyCreator创建的AOP代理对象之间的循环依赖,比如通用声明式Spring AOP代理、mybatis的代理、Spring声明式事务的代理,但是对于基于其他代理处理器创建的AOP代理则默认无法解决,比如Spring异步任务,它是通过通过AbstractAdvisingBeanPostProcessor来实现代理的,因此可能出现循环依赖异常。
上面讲的知识点,都是可以通过源码来得到的,因此下面我们来看看源码! 首先,我们要知道,最常见的可以创建代理对象的关键点(这里只讲最常见的两个点,还有其他的比如@Lazy、@Configuration等点位):
第一点:
这就是最常见地方,也就是在实例化和装配bean完毕之后的initializeBean
方法中,该方法中会调用applyBeanPostProcessorsAfterInitialization
方法,应用所有已注册的BeanPostProcessor后处理器的postProcessAfterInitialization方法。
Spring核心依赖中提供的后处理器在这个扩展点并没有做额外的事,但是如果我们引入了spring-aop的依赖,并且需要进行AOP代理,那么该方法就非常重要了,比如aop包中的AbstractAutoProxyCreator和AbstractAdvisingBeanPostProcessor
这两个后处理器都重写了该方法,都被用于根据原始对象来创建代理对象,进而执行AOP增强,比如自定义AOP、Spring事务、Spring Cache、Spring异步任务代理等操作。
第二点:
这是当有循环依赖的时候可能创建代理对象的地方。下面看源码。
在doCreateBean
方法中创建了ClassA的单例Bean实例之后,将会通过addSingletonFactory
方法向singletonFactories
缓存中加入一个ObjectFactory的实例,而实际上这是一个lambda的实例:
也就是说ObjectFactory对象的getObject
方法实际上就是调用这里的getEarlyBeanReference
方法。那么这个getEarlyBeanReference方法有什么特别吗?该方法中实际上就有可能对原始bean实例进行某些代理。
getEarlyBeanReference
方法内部将会应用全部SmartInstantiationAwareBeanPostProcessor
类型后处理器的getEarlyBeanReference
方法。
AbstractAutoProxyCreator
后处理器则重写了该方法,用于对对早期bean引用创建并返回代理对象。因此,这个地方目前仅能对基于AbstractAutoProxyCreator
体系创建代理对象的方式生成AOP代理对象,比如自定义AOP、Spring事务、Spring Cache等。如果是基于其他方式创建的代理对象,比如Spring异步任务就基于AbstractAdvisingBeanPostProcessor
,那在这里就不会执行这些AOP增强的逻辑。
明白了以上两点,那我们对于代理对象的循环依赖的情况就好理解和想象了。
假设ClassA类中定义了事务代理,并且只会基于AbstractAutoProxyCreator体系创建代理对象,自定义AOP、Spring事务、Spring Cache等。
首先初始化ClassA实例,然后通过addSingletonFactory方法向singletonFactories缓存中加入一个ObjectFactory的实例。
随后populateBean填充依赖,由于依赖了ClassB,因此会转而创建ClassB对象,ClassB创建完毕之后,又会通过populateBean填充依赖,由于依赖了ClassA,因此会转而获取ClassA对象。
由于缓存singletonFactories中具有classA的缓存,因此获取ClassA的ObjectFactory实例,然后调用getObject方法获取内部的实例,我们直到此时实际上是调用getEarlyBeanReference方法,并且由于ClassA将被会AbstractAutoProxyCreator代理,因此在这里就会基于早期ClassA实例创建代理对象,随后存入earlySingletonObjects中。因此,ClassB中的ClassA实际上就是一个代理对象,并且这个对象还被缓存到earlySingletonObjects中。
最后ClassA的populateBean方法返回之后,将会执行initializeBean方法继续初始化ClassA实例。我们前面说过,此时又是一个代理点,其内部会执行postProcessBeforeInstantiation方法也可能创建代理,那么这么做的话,是不是对ClassA就会应用两次代理了呢?并不是的,Spring已经做了防重处理。
此前的AbstractAutoProxyCreator的getEarlyBeanReference方法在调用时,会将当前原始bean实例存入earlyProxyReferences缓存中。当后续应用AbstractAutoProxyCreator的postProcessBeforeInstantiation方法时,会首先判断该缓存中是否具有当前beanName的缓存,如果有了的话,就不会再次代理了,而是直接返回原始bean实例。
initializeBean
方法执行完毕之后,获得的结果将会被赋给exposedObject实例,注意,exposedObject
最开始就是原始的bean实例。那么由于原始ClassA没有在initializeBean方法中被代理,那么此时返回的exposedObject
还是原始的bean实例。
下面就是一段关键的方法,非常重要,用于进行循环依赖的校验。
//如果允许暴露早期单例实例
if (earlySingletonExposure) {
/*
* 获取当前单例实例缓存,allowEarlyReference参数为false,因此只会尝试从singletonObjects和earlySingletonObjects中查找,没找到就返回null
*
* 如果不为null,说明存在循环依赖,此前另一个互相依赖的Bean通过getSingleton(beanName)获取当前bean实例时,
* 获取的结果就是ObjectFactory#getObject的返回值,实际上就是getEarlyBeanReference方法返回的结果,因此
* 最后还会将结果存入earlySingletonObjects缓存,因此这里获取的实际上就是另一个Bean注入的实例,可能是一个代理对象
*/
Object earlySingletonReference = getSingleton(beanName, false);
/*
* 如果earlySingletonReference不为null,说明存在循环依赖,并且此前已经调用了ObjectFactory#getObject方法,即getEarlyBeanReference方法。
* getEarlyBeanReference方法中,会回调所有SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法
* 因此将可能进行基于AbstractAutoProxyCreator的Spring AOP代理增强,比如自定义Spring AOP代理、Spring声明式事务等
*
* 这里获取的earlySingletonReference可能就是一个代理对象
*/
if (earlySingletonReference != null) {
/*
* 如果在经过populateBean和initializeBean方法之后返回的对象exposedObject还是等于原始对象bean,
* 即说明当前循环依赖的bean实例没有在populateBean和initializeBean这两个方法中调用的回调方法中被代理增强
* 或者只有基于AbstractAutoProxyCreator的Spring AOP代理增强,但是这在getEarlyBeanReference方法中已经被增强了
* Spring会保证基于AbstractAutoProxyCreator的增强只会进行一次,那么经过这两个填方法之后将仍然返回原始的bean
*
* 但是如果还有Spring @Async异步任务的AOP增强,由于它是通过AbstractAdvisingBeanPostProcessor来实现的,因此
* 经过这两个方法之后将返回一个新的代理的bean,这样exposedObject就和原来的bean不相等了
*/
if (exposedObject == bean) {
//那么exposedObject赋值为earlySingletonReference,对之前的循环引用没影响
//这里的earlySingletonReference可能就是被基于AbstractAutoProxyCreator代理增强的代理对象
exposedObject = earlySingletonReference;
}
//否则,说明该bean实例进行了其他的代理,比如Spring @Async异步任务
//继续判断如果不允许注入原始bean实例,并且该beanName被其他bean依赖
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
//获取依赖该bean的beanName数组
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
//遍历数组
for (String dependentBean : dependentBeans) {
/*
* 尝试将dependentBean对应的仅用于类型检查的已创建实例移除,如果是真正的创建的实例则不会一会,最后还会抛出异常
*
* 如果alreadyCreated缓存集合中不包含对应的dependentBean,说明该bean实例还未被真正创建,但是可能因为类型检查而创建
* 那么尝试移除对象缓存中的beanName对应的缓存(无论是否真正的因为类型检查而创建了),并返回true
* 如果alreadyCreated中包含对应的dependentBean否则,说明该bean实例因为其他用途已被创建了或者正在创建(比如真正的初始化该bean实例),
* 那么返回false,表示依赖该bean的bean实例可能已被创建了,但是注入的对象可能并不是最终的代理对象
* 因此后续将会抛出BeanCurrentlyInCreationException异常
*
* 在前面的doGetBean方法中,如果不是为了类型检查,那么会将即将获取的实例beanName通过markBeanAsCreated方法存入alreadyCreated缓存集合中
*/
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
//移除失败的dependentBean加入actualDependentBeans集合中
actualDependentBeans.add(dependentBean);
}
}
/*
* 如果actualDependentBeans集合不为空,那么表示可能有其他依赖该bean的实例注入的并不是目前最终的bean实例,那么将抛出异常。
*
* 实际上对于普通bean以及通用的AOP循环依赖注入以及事务循环依赖,Spring都可以帮我们解决循环依赖而不会抛出异常!
* 如果对@Async注解标注的类进行setter方法和反射字段注解的循环依赖注入(包括自己注入自己),就会抛出该异常。
* 而@Async类抛出异常的根本原因这个AOP代理对象不是使用通用的AbstractAutoProxyCreator的方法创建的,
* 而是使用AsyncAnnotationBeanPostProcessor后处理器来创建的,可以加一个@Lazy注解解决!
*/
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.");
}
}
}
}
首先是通过getSingleton(beanName, false)
尝试从singletonObjects和earlySingletonObjects缓存中获取ClassA的实例earlySingletonReference,这里获取的实际上就是此前ClassB在进行装配时获取的ClassA代理对象
。
随后由于initializeBean
方法中没有进行任何代理操作,因此exposedObject和bean就是同一个ClassA对象,即exposedObject == bean为true,因此会将exposedObject设置为earlySingletonReference,这样,最终返回的就是一个应用了所有AOP代理的代理对象,因此没有任何问题。
如果ClassA还会通过其他方式创建代理,那么就有很大的问题,比如ClassA中的有些方法是SPring异步任务方法。Spring异步任务也是通过AOP代理来实现的,并且将会通过AbstractAdvisingBeanPostProcessor的postProcessAfterInitialization方法来进行AOP增强,也就是只会在initializeBean方法中被增强。(Spring异步任务的源码我们在此前已经详细讲过了)。
当ClassA的populateBean方法执行完毕,我们知道ClassB已经完全初始化,但是其内部的ClassA却还有可能是原始的ClassA实例(ClassA没有AbstractAutoProxyCreator代理),或者仅仅进行了AbstractAutoProxyCreator代理的一个代理对象(ClassA具有AbstractAutoProxyCreator代理和其他代理,比如同时有Spring事务方法和异步任务方法)。
随后继续执行ClassA的initializeBean方法,这个方法中同样会进行AbstractAutoProxyCreator和其他代理,虽然在经过AbstractAutoProxyCreator时会判断此前已经代理过(如果ClassA具有AbstractAutoProxyCreator代理,那么就会在ClassB填充ClassA依赖时,从缓存获取的时候进行代理操作),但是在经过其他方式创建代理时,比如AbstractAdvisingBeanPostProcessor,那么仍然会执行代理操作,产生一个新的代理对象。因此这里的获取的exposedObject将是一个仅仅应用了Spring异步任务增强的代理对象,而AbstractAutoProxyCreator的增强则没有应用。
随后,在进行循环依赖的校验时。获取的earlySingletonReference没有被代理或者仅仅进行了AbstractAutoProxyCreator代理。
随后,会判断到exposedObject和原始bean不相等(因为已经进行了异步任务代理),那么将会走else if的逻辑,在该逻辑中,将会判断到有ClassB依赖了ClassA,并且ClassB实例已被创建(通过alreadyCreated缓存),并且此时ClassB中注入的代理对象仅仅是进行了AbstractAutoProxyCreator的增强,而没有进行Spring异步任务的增强。随后Spring选择直接抛出异常,这就是Spring默认无法解决通过其他方法创建代理时的出现的循环依赖的原因。
earlySingletonReference没有被代理或者仅仅进行了AbstractAutoProxyCreator代理,而exposedObject则仅仅进行了AbstractAdvisingBeanPostProcessor的代理,这是不对的,因为我们最终需要返回的是一个具有所有代理增强的代理对象,这两个对象都可能因为不是一个完整代理对象而无法作为最终的ClassA对象,因此就会抛出异常。
再回想一下,只有AbstractAutoProxyCreator代理的情况下,earlySingletonReference就是那一个代理对象,它已经应用了所有的代理增强,而ClassB中也是注入的这一个代理对象,那么最后返回的ClassA实例就可以使用这个代理对象,因此也不会抛出异常。
那么,对于具有其他AOP代理方式的bean出现循环依赖的时候该怎么解决呢?最简单的就是在字段上加一个@Lazy注解即可,此时该字段的注入将会延迟到被使用的时候,这样就不存在循环依赖了。
对于循环依赖,如果互相依赖的两个bean都没有在此前没Spring创建并持有,那么它们不能都是prototype的作用域。这一点我们从源码中能够看出来,具体的源码我们在此前已经详细讲解过了,这里我们简单的介绍以及进行总结。
首先,我们需要知道,如果一个Bean是prototype的作用域,那么在被创建时将和singleton作用域的bean走不同的创建路线:
可以看到,prototype的作用域的bean的beanName将会被加入到prototypesCurrentlyInCreation
缓存集合中。
其次,虽然同样是会调用createBean(beanName, mbd, args)
方法,但是和单例的bean有明显的不同点就是earlySingletonExposure
变量将为false!
也就是说创建的实例将不会被存入singletonObjects、earlySingletonObjects、singletonFactories
这几个缓存中。
有了上面的认识,那么关于prototype的作用域的bean的循环依赖为什么会抛出异常就很好理解了!
假设ClassA、ClassB都是prototype的。在创建ClasssA之后,同样会初始化它的依赖ClassB,ClassB创建完毕之后,又会查找它依赖的ClassA,但此时,在doGetBean
方法中,由于此前创建的ClassA实例没有被加入那几个缓存中,因此将会获取null,进而走创建ClassA的逻辑。
为了防止prototype的bean的循环创建,Spring在创建Bean实例逻辑的开始,就会判断是否存在正在被创建的prototype的bean被再次创建,实际上就是判断prototypesCurrentlyInCreation
缓存集合中是否包含当前beanName。
如果存在,则抛出BeanCurrentlyInCreationException
异常,这里,就导致抛出异常的关键原因。
因为解决bean的循环依赖使用的是缓存技术,但是prototype的语义就是每次都要获取一个新的对象,因此不能使用缓存,自然无法解决它们的循环依赖。
如果第一个bean为prototype,但是第二个是singleton,虽然它们循环依赖,然而在初始化容器的时候第二个单例bean会被被先创建,而第一个bean由于prototype的语义将会被延迟到在使用时才会被创建,到那个时候,第二个已被创建完毕,因此也不会抛出异常。
所以说,如果互相依赖的两个bean都没有在此前没Spring创建并持有,那么它们不能都是prototype的作用域。除非在互相依赖的属性上都加入@Lazy
注解,这样某个prototype
的bean在被创建时,它依赖的另一个prototype的bean会被延迟初始化。
如果看明白了此前的流程,那么为什么构造器循环依赖会报错就好解释了。
原理很简单,如果在实例化ClassA的时候发现了存在构造器依赖ClassB,那么会首先获取依赖的ClassA的bean,然后才会调用这个构造器创建ClassA实例。然而在初始化ClassB的实例时候呢,又会发现ClassB的构造器依赖了ClassA的实例,这样又必须先创建ClassA的实例。
我们此前说过解决循环依赖需要依靠早期实例对象,但是上面的情况下永远无法为某一方先创建早期实例,它们都需要对象的实例先被创建,创建实例所调用的构造器又需要依赖对方的已存在的实例,这样就会抛出异常!
那么,存在构造器循环依赖的时候,异常在哪里抛出的呢?就是在此前说的beforeSingletonCreation
方法中,如果singletonsCurrentlyInCreation
缓存集合中已存在当前的beanName,则说明存在构造器循环依赖。
经常说的所谓“三级缓存”,不外乎就是singletonObjects、earlySingletonObjects、singletonFactories
。
singletonObjects | 一级缓存,存放完整的 单例Bean实例。 |
earlySingletonObjects | 二级缓存,存放提前暴露的Bean,Bean 是不完整的,可能未完成populateBean属性注入和执行 initializeBean初始化方法。 |
singletonFactories | 三级缓存,存放的是 Bean的ObjectFactory,主要是生产 Bean实例并且进行代理,随后将结果存放到二级缓存中。 |
需要说明的是,“三级缓存”的概念并没有官方的说法,应该仅仅是某些开发者为了好理解而这么叫的。因为说到三级缓存就容易让人联想到CPU的三级缓存,进而可能会给人造成混淆,很明显的区别就是:这三个集合中的数据并不是包含关系,某个beanName及其对应的value数据,只能存在于这三个缓存集合中的某一个集合中。
可能被称为“三级缓存”的原因之一就是,这三个缓存之间存在着查找的先后顺序,这一点在DefaultSingletonBeanRegistry#getSingleton
方法中能够找到:
//--------DefaultSingletonBeanRegistry的相关属性---------
//Spring bean创建的"三级缓存":singletonObjects、earlySingletonObjects、singletonFactories
//可用于解决循环引用
/**
* beanName 到 bean instance 的单例bean实例map缓存
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* beanName 到 bean instance的早期单例bean实例map缓存
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* beanName 到 ObjectFactory的单例bean工厂实例map缓存
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 当前正在创建中的 bean 的名称
*/
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* DefaultSingletonBeanRegistry的方法
*
* 返回在给定名称下注册的(原始)单位对象。
* 检查已实例化的单例bean,并允许创建用来解决循环引用的早期引用。
*
* @param beanName 要查找的bean name
* @param allowEarlyReference 是否应创建早期引用
* @return 已注册的单位对象,如未找到,则为 null
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//单例bean实例的缓存
Object singletonObject = this.singletonObjects.get(beanName);
//如果没有创建bean实例,并且正在创建该beanName对应的单例bean
//即判断对应的singletonsCurrentlyInCreation缓存是否包含该beanName
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//加锁
synchronized (this.singletonObjects) {
//从早期的单例对象缓存中获取单例对象赋值给singletonObject,这些对象还未进行属性填充
singletonObject = this.earlySingletonObjects.get(beanName);
//如果singletonObject还是为null,并且允许创建早期引用
if (singletonObject == null && allowEarlyReference) {
//从单例工厂缓存中尝试指定获取beanName的单例工厂singletonFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//如果singletonFactory不为null
if (singletonFactory != null) {
//通过单例工厂创建一个单例对象
singletonObject = singletonFactory.getObject();
//存入早期单例对象缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//将该beanName对应的单例工厂移除,这个单例工厂已经没用了
//一个beanName的实例只能在"三级缓存"中的某一个缓存中
this.singletonFactories.remove(beanName);
}
}
}
}
//返回单例对象
return singletonObject;
}
/**
1. 返回指定的单例当前是否在创建中(在整个工厂内)。
2. 3. @param beanName bean name
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
singletonObjects
单例bean实例的缓存中获取给定beanName的实例singletonObject;singletonsCurrentlyInCreation
缓存中包含该beanName,我们知道,在调用createBean
方法前后,就会将beanName加入、移除该缓存:
earlySingletonObjects
中获取单例对象赋值给singletonObject
,这里的对象可能仅仅是被初始化了,还未进行属性填充,即早期实例,也可能是一个代理对象。singletonObject
还是为null,并且允许创建早期引用,即第二个参数allowEarlyReference
是true:
singletonFactories
中尝试指定获取beanName的单例工厂singletonFactory
,通过单例工厂创建一个单例对象。earlySingletonObjects
中,并将该beanName
对应的单例工厂从earlySingletonObjects
中移除,这个单例工厂已经没用了。singletonObject
。 allowCircularReferences
参数默认为true,我们可以调用beanFactory.setAllowCircularReferences(boolean)
方法自己配置,如果设置为true,那么很明显,当出现了循环依赖的时候就会抛出异常。
异常会在哪里抛出呢?实际上还是会在此前说的beforeSingletonCreation
方法中。
稍微想想:ClassA创建了实例之后会存入singletonFactories
缓存,然后会初始化它依赖的ClassB实例,ClassB初始化完毕之后又会查找它依赖的ClassA实例,然后虽然ClassA实例存在于singletonFactories
缓存中,但是由于allowCircularReferences
设置为false,那么getSingleton
将只会从另两个缓存中查找,自然是找不到的,那么找不到怎么办呢?随后又会开始初始化ClassA的实例,这样,在beforeSingletonCreation
方法中因为重复的初始化ClassA而抛出异常!
严格来讲,仅仅使用两个缓存,也可以来解决上面的循环依赖也是完全可以的,包括AOP情况下的循环依赖。
下面是使用第一、二级缓存的情况。我们可以直接在doGetBean
方法中,初始化早期对象实例之后,直接对该对象应用getEarlyBeanReference
方法的逻辑,即直接尝试创建代理对象,随后将返回的结果存入earlySingletonObjects
二级缓存中。
即图中的addSingletonFactory
方法改为以下两个方法:
Object earlyBeanReference = getEarlyBeanReference(beanName, mbd, bean);
addEarlyReference(beanName, earlyBeanReference);
addEarlyReference
方法如下:
protected void addEarlyReference(String beanName, Object earlyReference) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.earlySingletonObjects.put(beanName, earlyReference);
this.registeredSingletons.add(beanName);
}
}
}
改写getSingleton
方法:
/**
* 获取缓存实例
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName) && allowEarlyReference) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
return singletonObject;
}
}
return singletonObject;
}
改写addSingleton
方法:
/**
* getSingleton方法正常结束,某个bean被正常创建之后
* 将bean存入singletonObjects,并且从earlySingletonObjects中移除
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
这样的话,也是没什么问题的,那么两个缓存就能解决的问题,那么为什么Spring要采用三级缓存呢?
上面的方式,将会导致getEarlyBeanReference
方法每次都会被调用,即使是没有循环依赖,而实际上,大部分情况下都是没有循环依赖的情况,那么此时这个方法是没有什么作用的,这样就造成了无效调用!而从目前Spring的版本来看,getEarlyBeanReference
主要用于解决基于AbstractAutoProxyCreator
创建的Spring AOP的循环依赖,因此,如果没有循环依赖,那么这个方法根本就不应该被调用。
另外,调用getEarlyBeanReference
将导致在bean还没完全初始化的时候就进行代理对象的创建,我们应该在某个bean完全实例化和装配完毕之后再进行代理的创建,也就是在initializeBean
方法中执行代理,而在getEarlyBeanReference
方法中也会进行代理,应该是Spring为了让开发人员更加节省精力而设计的,这让可以静默的帮组程序员解决存在AOP时的循环依赖。实际上,如果我们的程序出现了循环依赖,那么大概率是我们的类、方法的设计有问题!
另外,三个缓存的设计能够让设计更加清晰,为开发人员提供了更多的扩展性!
setter方法和字段注解反射
的循环依赖注入(包括自己注入自己),当然这要求互相依赖的两个bean不能都是prototype原型的。构造器循环依赖注入
,但是我们可以使用@Lazy
注解手动解决,当然这要求构造器基于注解自动注入或者autowire=“constructor”。depends-on属性或者@DependsOn注解
造成的循环依赖的情况。占位符
循环依赖的情况。bean别名
循环映射的情况。@Import
循环引入bean的情况。< import/>标签和@ImportResource注解
循环引入配置文件的情况。基于XML的工厂方法和@Bean注解的方法
(同样会被解析为工厂方法)的循环依赖:如果互相依赖的工厂方法或者@Bean方法之间互相调用,或者互相注入的属性至少有一个出现在某个方法参数中并且该方法被Spring首先调用。如果是基于@Configuration的@Bean注解的方法,那么还要加一条:互相依赖的属性通过调用对方方法在本方法中设置并且该方法被Spring首先调用。如果互相依赖的属性都是注解配置的,那么可以解决。被@Async注解标注的类的setter方法和反射字段注解
的循环依赖注入(包括自己注入自己),但可以使用@Lazy
注解手动解决。相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!