SourceCode Java Spring Code

Spring解决循环依赖

Posted on 2021-03-15,4 min read

图解

Spring解决循环依赖
Spring循环依赖

解决方式

  • Spring使用了三级缓存 + 提前暴露对象的方式来解决循环依赖的问题。
  • 相关的重要属性:位于DefaultSingletonBeanRegistry类中
    • Spring内部维护了三个Map,所谓的三重缓存。
      • Map<String, Object> singletonObjects:单例池容器,用于缓存单例Bean的地方。一级缓存。
        • 这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存
      • Map<String, Object> earlySingletonObjects:早期的单例Bean。二级缓存。
        • 这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用
      • Map<String, ObjectFactory<?>> singletonFactories:创建中单例Bean的原始工厂。三级缓存。
        • 在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。
        • 在三级缓存中的Bean(提前暴露的Bean),如果被其他的Bean引用,就会从三级缓存中移动到二级缓存中。使得Spring能够感知到当前的Bean被其他引用了
    • Set<String> singletonsCurrentlyInCreation:保存了当前正在创建的单例对象的名称。
    • Map<String, Set<String>> dependentBeanMap:保存了一个bean对其他的bean的依赖。
  • 问题:
    • 如何解决循环依赖的:
      • 通过提前曝光解决循环依赖,当AB循环依赖的时候,先将A的半成品曝光出去,让B先完成初始化,然后在使得初始化完成后的B注入到A中。
    • 为什么要三级缓存:
      • 当某个 bean 进入到 2 级缓存的时候,说明这个 bean 的早期对象被其他 bean 注入了,也就是说,这个 bean 还是半成品,还未完全创建好的时候,已经被别人拿去使用了,所以必须要有 3 级缓存,2 级缓存中存放的是早期的被别人使用的对象,如果没有 2 级缓存,是无法判断这个对象在创建的过程中,是否被别人拿去使用了。
      • 三级缓存是为了判断循环依赖的时候,早期暴露出去已经被别人使用的 bean 和最终的 bean 是否是同一个 bean,如果不是同一个则弹出异常,如果早期的对象没有被其他 bean 使用,而后期被修改了,不会产生异常,如果没有三级缓存,是无法判断是否有循环依赖,且早期的 bean 被循环依赖中的 bean 使用了
      • Spring解决循环依赖的时候通过对象提前暴露来解决循环依赖,但是考虑到AOP和后置处理器的影响,此时暴露给其他Bean的当前对象可能不是最新的版本,因为通过AOP和后置处理器,此Bean的实际地址和其他Bean引用的地址不一样。这样就会导致其他Bean中引用的当前Bean不是最新版本。三级缓存的存在就是为了使得当前的Bean知道其他的Bean引用的不是自己的新版本。以便抛出异常。如下图,在调用InstantiationBean(使用了动态代理)之后实际上对象已经不一样了。
      • 在InstantiationBean中改变了Bean

下一篇: Spring Bean生命周期→

loading...