良心公众号
关注不迷路
菜鸡之所以讨论这个问题,主要有两方面的原因:一是因为,这是一道BAT级别的经典面试题;二是因为,工作中经常会涉及Spring循环依赖的相关问题。因此,特将该问题作如下整理,希望能给看到这篇文章的小伙伴或多或少的帮助。
当你在面试过程中和面试官聊到了Spring,那么多半会被问及Spring 如何解决循环依赖的问题。这是一道很能显水平的问题,这道面试题和谈谈HashMap的底层实现的问题很类似。因为这两道面试题都是答个七七八八容易,答得完整甚至完美很难,因为涉及的细节太多,除非吃透源码,否则很容易翻车。
典型的解决问题需要分三步走,是什么,为什么,怎么做。对应到本文就是:什么是循环依赖?为什么会产生循环依赖?怎么解决(或避免)循环依赖?
什么是循环依赖?
循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。
以上是循环依赖的基本概念。具体到Spring中,循环依赖的问题按照bean的注入方式不同,可以分为构造器注入的循环依赖,setter注入的循环依赖,注解注入的循环依赖。
为什么要分这三种呢?因为Spring本身无法解决构造器注入引起的循环依赖,也无法解决setter注入和注解注入的原型(@prototype)bean的循环依赖。而仅仅能解决setter注入和注解注入的单例bean的循环依赖。而在实际开发过程中,我们用得最多的就是单例bean。因此,不严格地说,Spring解决了大多数情况下的循环依赖。
我们为什么要讨论循环依赖?或者说面试官为什么喜欢问循环依赖?因为这涉及到了Spring bean的创建过程(原理层面),而且还很可能在实践过程中遇到相关的问题,或者说知道如何避免一些坑(实践层面)。
为什么会产生循环依赖?
随着业务的日渐复杂,循环依赖是很难彻底避免的。对于循环依赖的问题,菜鸡本着谨慎但又不过于担心,出现错误能够马上定位到问题的根源的态度,来看待循环依赖。
上文已经说到,Spring自身解决了大多数情况下的循环依赖,但这并不能说明该范围里的循环依赖都是合理的。有些循环依赖是可以通过良好的设计去避免的,这要取决于系统的模块划分,分层设计等一系列架构上的因素,在此不再展开。
怎么解决(或避免)循环依赖?
终于到了重头戏了!机智的小伙伴遇到问题的时候肯定更想知道该如何解决。且随菜鸡来看。
上文已经根据Spring bean的注入方式进行了分类,在此,我们依然沿用该分类,不同应用场景的问题有不同的处理方式。但在这里,我们先看一下,Spring是如何解决setter注入单例bean的循环依赖的,然后再据此看为什么构造器注入和注入原型bean的情况不能解决。
Spring通过三级缓存来解决单例bean的循环依赖。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); ... }
可以看到,三级缓存的底层数据结构就是三个Map。第一级缓存是单例缓存池singletonObjects,第二级缓存是提前暴露的对象缓存earlySingletonObjects,第三级缓存是单例对象工厂缓存singletonFactories。所谓的提前暴露的对象,可以简单的理解为声明了一个对象,但是并没有进行初始化。
接下来我们来看一下在创建单例bean的过程中,三级缓存是如何起作用的。首先,创建bean的过程想必小伙伴们已经有所了解,首先通过调用getBean方法,调用doGetBean方法。其部分源码如下:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { ... @Override public <T> T getBean(String name, Class<T> requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); } ... protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); ... return (T) bean; } }
在这个过程中,在doGetBean方法中,有一步是调用getSingleton方法,我们来剖析一下这部分源码。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { ... @Override @Nullable public Object getSingleton(String beanName) { return getSingleton(beanName, true); } @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } ... }
通过源码可以看出,该方法首先从一级缓存singletonObjects中获取bean的实例,如果获取不到,它会判断该对象是否正在创建当中,如果是的话,它就会从二级缓存earlySingletonObjects中获取,如果获取不到,它会判断是否允许从单例工厂对象缓存中获取对象,如果是的话,它就会从三级缓存singletonFactories中获取,然后将缓存级别提升至二级缓存。
在这之后,判断dependsOn数组是否满足要求。这个数组的内容是通过@DependsOn注解来指定的依赖关系,我们来看一下源码。
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { ... protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { ... try { final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } ... } ... } ... }
这里的逻辑是指,如果@DependsOn注解指定的依赖存在循环依赖,在doGetBean阶段会直接抛出BeanCreationException异常。也就是说,@DependsOn注解指定的依赖不允许存在循环依赖。
接下来,就是尝试createBean了,我们来看一下createBean的源码,看看这个方法里做了什么。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { ... @Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } } ... }
我们可以看到,在createBean方法中,有一步调用resolveBeforeInstantiation方法。它将尚未被实例化的Bean声明信息放进缓存,在代理目标对象的时候,可以直接在缓存中获取。
之后就是真正的bean的创建动作,调用doCreateBean方法。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { ... protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> 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 " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; } ... }
欣赏着这迷人的代码,让人似乎忘了这篇文章在干嘛。没错,是在讨论循环依赖!
我们看到,在doCreateBean方法中调用了createBeanInstance方法去创建Bean的实例。
如果创建成功,就通过调用addSingletonFactory方法把该对象放入三级缓存中,并将对象从二级缓存中移除。
然后通过调用populateBean方法给对象的属性赋值。
至此,我们已经了解了单例bean的创建过程,脑海中模拟一下两个相互依赖的单例对象的创建过程,也就相应地清楚了Spring是如何通过三级缓存解决单例bean的循环依赖问题的。
那么为何构造器注入的循环依赖和原型bean的循环依赖无法通过三级缓存解决呢?
在createBeanInstance方法中在创建Bean的实例之前,会检测构造器的依赖关系。比如说,如果在创建A对象的时候,发现了A的构造器里依赖了B,就会重新走getBean的这个流程,当再走到这里的时候,又发现B的构造器里依赖了A,此时就会抛出异常。
之所以会抛出异常,还要从最初的getBean方法入手分析,在getBean方法中,会尝试依次从一级缓存、二级缓存和三级缓存中获取bean,但此时bean尚未被创建好,也就是说缓存中不存在这个bean,所以在缓存中拿不到B对象。同理,也拿不到A对象。于是产生了死循环。
这就是Spring三级缓存不能解决构造器循环依赖的原因。
接下来,我们再看,原型bean的循环依赖为什么不能解决。细心的小伙伴已经发现了,在doGetBean方法中,有这样一段逻辑:
// Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }
也就是说,如果该原型bean正在被创建,那么会直接抛出异常。到这里三种情况都已经说明了,但菜鸡最近还遇到了一个坑,与上面几种不尽相同。菜鸡在采用注解进行单例bean注入的时候,发现Spring报出了循环依赖的错误。菜鸡一度觉得很神奇,经过一番排查,发现在产生循环依赖的类中,有些方法使用了@Transactional注解。相对应的BeanPostProcessor中会生成相应的代理对象,在doCreateBean方法中有一个if判断。我们看一下源代码。
Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> 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 " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } }
由于代理对象和之前的对象不是同一个对象,就会走入else if的逻辑,从而抛出BeanCurrentlyInCreationException异常。与之类似的还有@Async注解,在使用这些注解的时候,要警惕循环依赖的问题。
至此,菜鸡对于循环依赖的分析已经结束了,是不是觉得阅读源码很有收获,尤其是带着问题阅读源码!
本篇文章就到这里了,由于菜鸡水平有限,难免出现谬误,望大佬们不吝赐教,同时也欢迎有问题的小伙伴留言,私信,一起讨论技术,一起成长!
学习 | 工作 | 分享
????长按关注“有理想的菜鸡”
只有你想不到,没有你学不到