Spring循环依赖探究

2023年 10月 11日 75.6k 0

1. 前言

Spring在较新版本中已经默认不允许bean之间发生「循环依赖」了,如果检测到循环依赖,容器启动时将会报错,此时可以通过配置来允许循环依赖。

spring.main.allow-circular-references=true

什么是循环依赖?
循环依赖也叫循环引用,简单点说,就是bean之间的依赖关系形成了一个循环,例如beanA依赖beanB,beanB又依赖beanA。
image.png

@Component
public class A {

    @Autowired
    B b;
}

@Component
public class A {

    @Autowired
    B b;
}

如上代码所示,Spring本身是支持循环依赖的,Spring加载bean时,首先会实例化A,然后对A做初始化,其中就包含属性填充,填充属性时发现A依赖B,于是Spring又会从容器中去加载B,创建B时发现B又依赖了A,循环依赖就此产生,Spring是如何打破这个循环的呢?

2. 依赖注入的方式

Spring完成依赖注入的方式有三种:

  • 属性注入
  • Setter方法注入
  • 构造函数注入
  • 对于构造函数注入的方式,循环依赖是无解的,Spring也无法支持。属性注入和Setter方法注入原理上是一样的,本文通过属性注入的方式来分析Spring是如何支持循环依赖的。

    循环依赖仅限于单例bean,对于原型bean,循环依赖也是不允许的。

    3. bean的多级缓存

    Spring支持循环依赖的核心是bean的三级缓存。
    Spring会在启动的时候,加载容器内所有非lazy的单例bean,方法是DefaultListableBeanFactory#preInstantiateSingletons(),该方法会把容器内所有非lazy的单例beanName拿出来,然后依次调用getBean()方法获取bean。
    getBean()时,首先会调用getSingleton()方法优先从缓存中获取bean,只要缓存中存在bean,就可以直接获取了,避免二次创建。
    image.png
    核心就在getSingleton()方法中,Spring会在各级缓存中查找bean。

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 检查一级缓存,是否存在bean
        Object singletonObject = this.singletonObjects.get(beanName);
        // bean是否创建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                /**
                 * 二级缓存查找
                 * 二级缓存意义不大?没有二级缓存也能解决循环依赖,作用是啥?
                 * 三级缓存的ObjectFactory#getObject()方法是有代价的,会触发后置处理器获取早期引用
                 * @see SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
                 * 频繁触发势必带来性能问题,引入二级缓存,一旦getObject()触发就移入二级缓存
                 */
                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;
    }
    

    该方法做了几件事:

  • 一级缓存中是否存在bean?存在直接返回bean。
  • 一级缓存没有,且bean正在创建中,则去二级缓存查找。
  • 二级缓存没有,且允许循环依赖,则去三级缓存查找。
  • 三级缓存并没有直接存储bean,而是存储bean的工厂方法对象,通过调用工厂方法来获得bean,一旦通过三级缓存得到了bean,则将其从三级缓存移除并加入到二级缓存中。
  • 所谓的多级缓存,其实就是三个Map容器。

    /**
    * 一级缓存,完整的bean容器
    * 经历了完整生命周期的bean
    */
    private final Map singletonObjects = new ConcurrentHashMap(256);

    /**
    * 二级缓存,提前暴露的bean,只实例化了,还未初始化
    */
    private final Map earlySingletonObjects = new HashMap(16);

    /**
    * 三级缓存,不直接缓存bean
    * 而是提前暴露的bean的工厂方法,调用方法来产生bean
    * 对于需要AOP增强的bean,此时就需要生成带来对象了
    */
    private final Map

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论