阿里妹导读
本文主要梳理了Spring解决bean循环依赖的思路。
一、背景
有好几次线上发布老应用时,遭遇代码启动报错,具体错误如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean withname'xxxManageFacadeImpl': Bean withname'xxxManageFacadeImpl' has been injected into other beans [xxxProductMaintenceFacadeImpl]in its rawversionas part of a circular reference, but has eventually been wrap means thff, for expped. This means that said other beans donotuse the finalversionof the bean. This is often the resultofover-eager type matching - considerusing'getBeanNamesallowEageOfType'with the 'allowEagerInit' flag turned off, for example
眨眼一看,这不就是Spring Bean循环依赖报错吗?脑海立马闪过那些年为了进阿里面试时被死亡N连问的场景,那时我们都知道Spring已经支持bean循环依赖,为啥我们的Springboot应用启动时还报这个错误?带着这个问题于是要重新温习下Spring如何解决bean循环依赖。
二、相关知识点简介
2.1、什么是Bean循环依赖?
循环依赖是指Bean对象循环引用,是两个或多个Bean之间相互持有对方的引用。循环依赖有2种表现形式:
第一种是相互依赖,也就是A依赖B,B又依赖A;

图一 相互依赖示例图
第二种是自我依赖,也就是A依赖自己形成自我依赖。
图二 自我依赖示例图
对象引用循环依赖在某些业务场景上可能是合理存在的,但是由于Spring容器设计了依赖注入机制,即Spring容器在创建bean实例化以后就要给bean中的属性自动赋值,要全部自动赋值之后,才能交给用户使用。如果出现循环依赖的情况,以两个bean互相依赖的情况作为举例,假设有AService已经实例化(但未完成初始化),但是AService中需要自动赋值的BService并没有初始化,如果Spring立刻初始化BService,发现BService中需要自动赋值AService也没有初始化完成,这样就会出现相互等待,形成死循环,可能导致Spring容器都无法启动了。
由此可见,对Bean的填充属性是循环依赖源头的开始。
2.2、Spring创建Bean主要流程
为了容易理解Spring解决循环依赖过程,我们先简单温习下Spring容器创建Bena的主要流程。
从代码看Spring对于Bean的生成过程,步骤还是很多的,我把一些扩展业务代码省略掉,先上点开胃菜:
protectedObject doCreateBean(final String beanName, final RootBeanDefinition mbd, final @NullableObject[] args) throws BeanCreationException {if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); }// Bean初始化第一步:默认调用无参构造实例化Bean// 如果是只有带参数的构造方法,构造方法里的参数依赖注入,就是发生在这一步if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); }// Initialize the bean instance.Object exposedObject = bean;try {// bean创建第二步:填充属性(DI依赖注入发生在此步骤) populateBean(beanName, mbd, instanceWrapper);// bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口)// AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()//方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现 exposedObject = initializeBean(beanName, exposedObject, mbd); }catch (Throwable ex) {// ... }// ...
从上述代码看出,整体脉络可以归纳成3个核心步骤:
1. 实例化Bean
主要是通过反射调用默认构造函数创建Bean实例,此时bean的属性都还是默认值null。被注解@Bean标注的方法就是此阶段被调用的。
2. 填充Bean属性
这一步主要是对bean的依赖属性进行填充,对@Value @Autowired @Resource注解标注的属性注入对象引用。
3. 调用Bean初始化方法
调用配置指定中的init 方法,如xml文件指定bean的init-method方法或注解@Bean(initMethod = "initMethod")指定的方法。
2.3、Bean创建过程BeanPostProcessor接口拓展点
在Bean创建的流程中Spring提供了多个BeanPostProcessor接口(下称BPP)方便开发者对Bean进行自定义调整和加工。有以下几种BPP接口比较常用:
  • postProcessMergedBeanDefinition:可对BeanDefinition添加额外的自定义配置
  • getEarlyBeanReference:返回早期暴露的bean引用,一个典型的例子是循环依赖时如果有动态代理,需要在此先返回代理实例
  • postProcessAfterInstantiation:在populateBean前用户可以手动注入一些属性
  • postProcessProperties:对属性进行注入,例如配置文件加密信息在此解密后注入
  • postProcessBeforeInitialization:属性注入后的一些额外操作
  • postProcessAfterInitialization:实例完成创建的最后一步,这里也是一些BPP进行AOP代理的时机.

    最后,对bean的生命流程进行一个流程图的总结

    图三 bean的生命流程图
此处敲黑板划重点:Spring的动态代理(AOP)是通过BPP实现的(在图三中的3.4步实现),其中AbstractAutoProxyCreator是十分典型的自动代理类,它实现了SmartInstantiationAwareBeanPostProcessor接口,并重写了getEarlyBeanReference和postProcessAfterInitialization两个方法实现代理的逻辑,这样完成对原始Bean进行增强,生成新Bean对象,将增强后的新Bean对象注入到属性依赖中。
三、Spring如何解决循环依赖?
先说结论,Spring是通过三级缓存和提前曝光的机制来解决循环依赖的问题。
3.1、三级缓存作用
三级缓存其实就是用三个Map来存储不同阶段Bean对象。


一级缓存 singletonObjects: 主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例Bean实例,这样的Bean能够直接提供给用户使用,我们称之为终态Bean或叫成熟Bean。
二级缓存 earlySingletonObjects: 主要存放的已经完成初始化但属性还没自动赋值的Bean,这些Bean还不能提供用户使用,只是用于提前暴露的Bean实例,我们把这样的Bean称之为临时Bean或早期的Bean(半成品Bean) 
三级缓存 singletonFactories: 存放的是ObjectFactory的匿名内部类实例,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,该方法可以获取提前暴露的单例bean引用。
3.2、三级缓存解决循环依赖过程
现在通过源码分析,深入理解下Spring如何运用三级缓存解决循环依赖。Spring创建Bean的核心代码doGetBean中,在实例化bean之前,会先尝试从三级缓存获取bean,这也是Spring解决循环依赖的开始。
我们假设现在有这样的场景AService依赖BService,BService依赖AService
一开始加载AService Bean首先依次从一二三级缓存中查找是否存在beanName=AService的对象。
// AbstractBeanFactory.javaprotected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name);// 1.尝试从缓存中获取bean,AService还没创建三级缓存都没命中Object sharedInstance = getSingleton(beanName);if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { //注意此处参数是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象try {return createBean(beanName, mbd, args); // }catch (BeansException ex) {} }); beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } }
因为AService还没创建三级缓存都没命中于是走到创建Bean代码逻辑。调用方法getSingleton(String beanName,ObjectFactory objectFactory)方法,第二个参数传入一个ObjectFactory接口的匿名内部类实例。
publicObject getSingleton(String beanName, ObjectFactory singletonFactory) {//将当前beanName放到singletonsCurrentlyInCreation 集合中,标识该bean正在创建 beforeSingletonCreation(beanName);//通过回调getObject()方法触发AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行 singletonObject = singletonFactory.getObject(); afterSingletonCreation(beanName); addSingleton(beanName, singletonObject);}
该方法主要做四件事情:
  • 将当前beanName放到singletonsCurrentlyInCreation集合中标识该bean正在创建;
  • 调用匿名内部类实例对象的getObject()方法触发AbstractAutowireCapableBeanFactory#createBean方法的执行;
  • 将当前beanName从singletonsCurrentlyInCreation集合中移除;
singletonFactory.getObject()方法触发回调AbstractAutowireCapableBeanFactory#createBean(String beanName, RootBeanDefinition mbd, Object[] args)的执行,走真正创建AService Bean流程。
//真正创建Bean的地方 AbstractAutowireCapableBeanFactory#doCreateBeanprotectedObject doCreateBean(final String beanName, final RootBeanDefinition mbd, final @NullableObject[] args) throws BeanCreationException {// Instantiate the bean. BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); }// bean初始化第一步:默认调用无参构造实例化Bean// 构造参数依赖注入,就是发生在这一步if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); }// 实例化后的Bean对象 final Object bean = instanceWrapper.getWrappedInstance();// 将刚创建的bean放入三级缓存中singleFactories(key是beanName,value是ObjectFactory)//注意此处参数又是一个lambda表达式即参数传入的是ObjectFactory类型一个匿名内部类对象,在后续再缓存中查找Bean时会触发匿名内部类getEarlyBeanReference()方法回调 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// Initialize the bean instance.Object exposedObject = bean;try {// bean创建第二步:填充属性(DI依赖注入发生在此步骤) populateBean(beanName, mbd, instanceWrapper);// bean创建第三步:调用初始化方法,完成bean的初始化操作(AOP的第三个入口)// AOP是通过自动代理创建器AbstractAutoProxyCreator的postProcessAfterInitialization()//方法的执行进行代理对象的创建的,AbstractAutoProxyCreator是BeanPostProcessor接口的实现 exposedObject = initializeBean(beanName, exposedObject, mbd); }catch (Throwable ex) {// ... } }
在上面创建AService Bean代码流程可以看出,AService实例化后调用addSingletonFactory(String beanName, ObjectFactory singletonFactory) 方法将以Key为AService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存中,在后续使用AService时会依次在一二三级缓存中查找,最终三级缓存中查到这个匿名内部类对象,从而触发匿名内部类中getEarlyBeanReference()方法回调。
此处为什么不是AService实例直接放入三级缓存呢?因为我们上面说了AOP增强逻辑是在创建Bean第三步:调用初始化方法之后进行的,AOP增强后生成的新代理类AServiceProxy实例对象,假如此时直接把AService实例直接放入三级缓存,那么在对BService Bean依赖的aService属性赋值的就是AService实例,而不是增强后的AServiceProxy实例对象。
在以Key为AService,value为ObjectFactory类型一个匿名内部类对象放入三级缓存后,继续对AService进行属性填充(依赖注入),这时发现AService依赖BService。
于是又依次从一二三级缓存中查询BService Bean,没找到,于是又按照上述的流程实例化BService,将以Key为BService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存中,继续对BService进行属性填充(依赖注入),这时发现BService又依赖AService。于是依次在一二三级缓存中查找AService。
//DefaultSingletonBeanRegistry.javapublic Object getSingleton(String beanName) {return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存获取,key=AService Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) {// 从二级缓存获取,key=AService singletonObject = this.earlySingletonObjects.get(beanName);// 是否允许循环引用if (singletonObject == null && allowEarlyReference) {// 前面已经将以Key为AService,value是ObjectFactory类型一个匿名内部类对象放入三级缓存了 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//singletonFactory是一个匿名内部类对象,此处触发匿名内部类中getEarlyBeanReference()方法回调。 singletonObject = singletonFactory.getObject();// 将三级缓存生产的bean放入二级缓存中this.earlySingletonObjects.put(beanName, singletonObject);// 删除三级缓存this.singletonFactories.remove(beanName); } } } }return singletonObject; }
最终三级缓存中查到之前放入的以Key为AService,value为ObjectFactory类型一个匿名内部类对象,从而触发匿名内部类getEarlyBeanReference()方法回调。getEarlyBeanReference()方法决定返回AService实例到底是AService实例本身还是被AOP增强后的AServiceProxy实例对象。如果没AOP切面对AService进行拦截,这时返回的将是AService实例本身。接着将半成品AService Bean放入二级缓存并将Key为AService从三级缓存中删除,这样实现了提前将AService Bean曝光给BService完成属性依赖注入。继续走BService后续初始化逻辑,最后生产了成熟的BService Bean实例。
接着原路返回,AService也成功获取到依赖BService实例,完成后续的初始化工作,然后完美的解决了循环依赖的问题。
最后,来一张解决AService依赖BService,BService又依赖AService这样循环依赖的流程图对上述Spring代码逻辑进行总结。
图四 没有AOP的Bean循环依赖解决的流程图
3.3、 当AOP遇到循环依赖
2.3、Bean创建过程BeanPostProcessor接口拓展小节,我们知道Bean的AOP动态代理创建时在初始化之后通过回调postProcessAfterInitialization后置处理器进行的,但是出现循环依赖的Bean如果使用了AOP, 那就需要在getEarlyBeanReference()方法创建动态代理,将生成的代理Bean放在二级缓存提前曝光出来, 这样BService的属性aService注入的就是被代理后的AServiceProxy实例对象。
下面以AService依赖BService,BService依赖AService,AService被AOP切面拦截的场景进行代码分析循环依赖的Bean使用了AOP如何在getEarlyBeanReference()方法如何提前创建动态代理Bean。

// 将Aservice添加三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 添加Bservice的aService属性时从三级中找Aservice的ObjectFactory类型一个匿名内部类对象,从而触发匿名内部类getEarlyBeanReference()方法回调,进入创建AService切面代理对象逻辑protectedObject getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {//判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor接口//调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReferencefor (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } }return exposedObject;}
可以看出getEarlyBeanReference()方法判断后置处理器是否实现了SmartInstantiationAwareBeanPostProcessor后置处理器接口。
而我们演示代码通过@EnableAspectJAutoProxy注解导入的AOP核心业务处理AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。
//真正实现了该方法的类就是AbstractAutoProxyCreatorpublicabstractclassAbstractAutoProxyCreatorextendsProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Overridepublic Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {// 先获取beanName,主要是为FactoryBean类型添加&前缀 Object cacheKey = getCacheKey(bean.getClass(), beanName);// 判断是否已经在earlyProxyReferences集合中,不在则添加进去if (!this.earlyProxyReferences.contains(cacheKey)) {this.earlyProxyReferences.add(cacheKey); }// 创建代理对象,如果必要的话return wrapIfNecessary(bean, beanName, cacheKey); } /** * Wrap the given bean if necessary, i.e. if it is eligible for being proxied. * @param bean the raw bean instance * @param beanName the name of the bean * @param cacheKey the cache key for metadata access * @return a proxy wrapping the bean, or the raw bean instance as-is */protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 前面先做一些基本的判断if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean; }if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean; }// Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不进行代理以及对beanName为aop内的切面名也不进行代理// 此处可查看子类复写的shouldSkip()方法if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean; }// Create proxy if we have advice.// 查找对代理类相关的advisor对象集合,此处就与point-cut表达式有关了 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 对相应的advisor不为空才采取代理if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 通过jdk动态代理或者cglib动态代理,产生代理对象,这里传入的是SingletonTargetSource对象喔,对原始bean对象进行了包装 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 放入代理类型缓存this.proxyTypes.put(cacheKey, proxy.getClass());return proxy; }// 放入通知缓存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean; }}
wrapIfNecessary 方法查找AService是否查找存在的advisor对象集合,此处就与point-cut表达式有关了,显然我们的切点 @Around("execution(* com.example.service.AService.helloA(..))")
拦截了AService,因此需要创建AService的代理Bean。通过jdk动态代理或者cglib动态代理,产生代理对象,对原始AService对象进行了包装最后返回的是 AService的代理对象aServiceProxy,然后把 aServiceProxy 放入二级缓存里面,并删除三级缓存中的 AService的ObjectFactory。这样实现了提前为AService生成动态对象aServiceProxy并赋值给BService的aService属性依赖注入。这样BService完成了属性依赖注入,继续走BService后续初始化逻辑,最后生产了成熟的BService Bean实例。当 BService创建完了之后, AService在缓存BService Bean对象完成bService属性注入后,接着走到Bean创建流程的第三步:初始化AService,有上面知识我们知道初始化AService会回调postProcessAfterInitialization后置处理器又开始AOP逻辑。


而此时判断 AService已经存在getEarlyBeanReference()方法中放入earlyProxyReferences了,说明 原始对象已经经历过了AOP,因此就不用重复进行AOP逻辑。
这样AService也完成初始化工作,然后完美的解决了Aservice依赖BService,BService依赖Aservice这个循环依赖的问题。
最后,也来一张解决AService、BService相互依赖,且AService使用了AOP的循环依赖的流程图对上述Spring代码逻辑进行总结。红色部分主要与没有AOP情况AService、BService相互依赖流程区别内容。

图五 使用AOP且出现循环依赖的解决流程图
四、为啥我们应用还会报错
前面章节已经详细讲了Spring通过三级缓存和提前曝光机制解决循环依赖问题。那我们的应用怎么还报此类错误呢?首先回顾下报错详情:

从错误描述看xxxProductMaintenanceFacadeImpl注入的xxxManageFacadeImpl对象与最终的xxxManageFacadeImpl对象不一致。从上面代码分析,我们知道Spring能改变单例Bean的对象只有在AOP情况下出现,而出现循环依赖且使用AOP的Bean有getEarlyBeanReference()方法和bean初始化步骤里后置处理器postProcessAfterInitialization两处时机进行AOP,如图五中第18步和第22步。如果是同一个AOP的织入类,那么在bean初始化步骤里后置处理器postProcessAfterInitialization处会判断Bean已经被代理过,不会再做AOP代理。但现在报错xxxManageFacadeImpl对象最终版本不一致,说明XxxManageFacadeImpl存在另一个AOP的织入类且是在后置处理器postProcessAfterInitialization处进行AOP的。
以下模拟我们的项目代码:


从示例代码看出AServiceImpl类被@Aspect和@Async两个切面注解拦截。
@Aspect注解的AOP核心业务处理由AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。
@Async注解的AOP核心业务处理由AsyncAnnotationBeanPostProcessor类,它只实现了postProcessAfterInitialization()方法,至于为什么@Async不实现提早暴露getEarlyBeanReference(),我还没有想明白。这样@Async注解是在AService初始化步骤里后置处理器postProcessAfterInitialization进行AOP,新生成了AServiceProxy2对象。
如下图所示@Aspect注解的AOP是在第18步实现的,这样二级缓存里的存放和BService对象的aService属性注入都是AServiceProxy实例对象;
而@Async注解的AOP是在第22步实现的,这是新生成AServiceProxy2实例对象;下图中蓝色部分就是进行两次AOP地方。
那么单例Bean AService存在两个AOP后的实例对象,这就违背单例的单一性原则,因此报错了;
图六 两个AOP代理时机不同导致生成两个代理Bean实例对象
或许到此你还会疑问,这个循环依赖问题为什么日常或预发没出现,而都是线上部署时才遇到报错此错误?
这就跟Spring的Bean加载顺序有关系了, Spring容器载入bean顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。在某些机器环境下是AService比BService先加载,但在某些环境下是BService比AService先加载。
还是拿上面示例分析,AServiceImpl
类被@Aspect和@Async两个切面注解拦截,但是先加载BService再加载AService。


由图可以看出AService的@Aspect和@Async两个注解AOP在都是在后置处理器进行,因此只生成一个代理对象AServiceProxy实例,这种情况下应用启动就不会报错。
五、总结
总结下Spring解决循环依赖的思路:
在创建单例bean时,会把该bean的工厂函数的匿名类对象放入三级缓存中的singletonFactories中;
然后在填充属性时,如果出现循环依赖依赖本 bean,必然执行之前放入的工厂函数的匿名实现,如果该bean无需 AOP的话,工厂函数返回的就是原bean对象;如果该bean有 AOP 的话,也有可能是被某些BBP处理AOP 之后的代理对象,会放入二级缓存中的earlySingletonObjects中;
接着bean开始初始化,如果该bean无需 AOP的话,结果返回的原来创建的bean对象;如果该bean有 AOP 的话,检查AOP织入逻辑是否已经在提前曝光时已经执行了,如果已经执行AOP则返回提前曝光的代理bean对象;如果AOP织入逻辑未执行过,则进行后续的 BeanPostProcessor后置处理器进行AOP织入,生成AOP代理bean对象,并返回。
最后对于提前曝光的单例,就会去检查初始化后的bean对象与二级缓存中提前曝光的bean是不是同一个对象,只有不是的情况下才可能抛出异常。
深入阅读我们应用本身代码,发现项目中出现Bean的循环依赖,本质原因是代码架构设计不合理,某些facade类实现了本应在serivce层的业务逻辑,导致其他业务依赖地方反复应用facade层对象。SpringBoot 2.6.x以上的版本官方已经不推荐使用循环依赖,说不定今后某个最新版本的Spring会强制不能出现Bean循环依赖,因此需要我们开发者在平时编码时要重视代码架构设计。
阿里云开发者社区,千万开发者的选择
阿里云开发者社区,百万精品技术内容、千节免费系统课程、丰富的体验场景、活跃的社群活动、行业专家分享交流,欢迎点击【阅读原文】加入我们。
继续阅读
阅读原文