前一段时间,阿粉的读者给阿粉留言,说在面试的时候,有个面试官就问她,Spring 的各种知识,Spring 的生命周期, Spring 的循环依赖是如何解决的。
就这么几个问题,虽然回答的不是很好,但是也是很幸运的接到了 offer ,毕竟面试一般很少会因为一两个面试题回答的不好,就直接 pass 的,还是看综合表现的,既然问到阿粉这个 Spring 是如何处理循环依赖的了,那么阿粉就得来解释一下,Spring 是如何处理循环依赖的。
循环依赖
什么是循环依赖,说到循环依赖,这个实际上是没有那么复杂的,就比如很简单的说,A 引用了 B ,而这个时候 B 也引用了 A ,那么这种情况实际上就是出现了循环依赖的问题了,实际上也可以把循环依赖称之为循环引用,两个或者两个以上的bean互相持有对方,最终形成闭环。
这就是循环依赖,也就是循环引用,
注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。否则的话,他就是一个死循环.
Spring 中的循环依赖
那么 Spring 的循环依赖都有什么呢?
那么针对这两种循环依赖,Spring 它是如何解决的呢?这就很特殊了,构造器的循环依赖问题实际上算是个无解的操作,只能拋出 BeanCurrentlyInCreationException 异常,也就是说,这个构造器导致的循环依赖,Spring 是没有办法来处理的,也只是给抛出了异常,但是对于 字段属性 的循环依赖,还是有解决办法的。
Spring怎么解决循环依赖
这个时候,我们就得看看 Spring 的对象初始化的过程了,
Spring的单例对象的初始化主要分为三步:
createBeanInstance 实例化实际上就是调用对象的构造方法实例化对象,populateBean 实际上就是对 bean 的依赖属性进行一个赋值填充,而 initializeBean 则是调用 Spring xml 中的 init 方法。
这个时候,我们看到这个初始化的过程,一般就应该能猜到会发生 循环依赖 的位置是哪一步了,而单从 bean 的初始化来看,循环依赖发生的位置就是在 createBeanInstance 实例化 以及 populateBean 填充属性 当中,
发生的循环依赖也是
那么 Spring 又是怎么解决这种单例的循环依赖的问题的呢?
三级缓存
那么这三级缓存分别是哪三级的缓存呢?又分别代表了什么含义?
private final MapsingletonObjects = new ConcurrentHashMap<>(256); //一级缓存 private final Map earlySingletonObjects = new HashMap<>(16); // 二级缓存 private final Map > singletonFactories = new HashMap<>(16); // 三级缓存
如果要分析这个 三级缓存 如何解决循环依赖,那么势必需要知道 Spring 中对象的创建的过程。
对象创建过程,可以大致分为五个步骤,
1.protected
AbstractBeanFactory 中的 doGetBean()方法
2.protected Object getSingleton(String beanName, boolean allowEarlyReference)
DefaultSingletonBeanRegistry 中的 getSingleton()方法
源码如下:
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; }
3.protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
AbstractAutowireCapableBeanFactory 中的 doCreateBean() 方法
//添加到三级缓存 if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
4.protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)
AbstractAutowireCapableBeanFactory 中的 populateBean() 方法进行属性赋值
5.protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
AbstractAutowireCapableBeanFactory 中的 initializeBean() 初始化对象
源码部分阿粉就不再往上贴那么多了,大家找源码肯定很简单,内部也有具体方法的注释,
Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级cache。
这个 cache 的类型是 ObjectFactory。这里就是解决循环依赖的关键,发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。
这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
如果你能在面试的时候,回答成这个样子,那么这个问题,你至少已经算是回答的比较好了。
但是如果问到这里,面试官有意想要继续深挖一下,你既然知道使用三级缓存解决了这个循环依赖的问题了,那么是不是必须三级缓存才能解决,二级缓存不能解决吗?
这就另外又给你引申出一个问题来了,二级缓存到底能不能解决呢?
其实,二级缓存也是能够实现的,如果自己想要实现,那么就得去改写 AbstractAutowireCapableBeanFactory 的 doCreateBean 的方法了,
//添加到三级缓存 if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); //从三级缓存中取出立刻放入二级缓存 getSingleton(beanName, true); }
如果要使用二级缓存解决循环依赖,意味着Bean在构造完后就创建代理对象,这样违背了Spring设计原则。
Spring结合AOP跟Bean的生命周期,是在Bean创建完全之后通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。
如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
所以,你知道为什么不使用二级缓存直接来处理了,而是增加了三级缓存来处理这个循环依赖了吧!