什么是循环依赖

1,658 阅读3分钟

循环依赖

循环引用

循环依赖:是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用

Spring 循环依赖有四种:

  • DependsOn 依赖加载【无法解决】(两种 Map)
  • 原型模式 Prototype 循环依赖【无法解决】(正在创建集合)
  • 单例 Bean 循环依赖:构造参数产生依赖【无法解决】(正在创建集合,getSingleton() 逻辑中)
  • 单例 Bean 循环依赖:setter 产生依赖【可以解决】

解决循环依赖:提前引用,提前暴露创建中的 Bean

  • Spring 先实例化 A,拿到 A 的构造方法反射创建出来 A 的早期实例对象,这个对象被包装成 ObjectFactory 对象,放入三级缓存
  • 处理 A 的依赖数据,检查发现 A 依赖 B 对象,所以 Spring 就会去根据 B 类型到容器中去 getBean(B),这里产生递归
  • 拿到 B 的构造方法,进行反射创建出来 B 的早期实例对象,也会把 B 包装成 ObjectFactory 对象,放到三级缓存,处理 B 的依赖数据,检查发现 B 依赖了 A 对象,然后 Spring 就会去根据 A 类型到容器中去 getBean(A.class)
  • 这时从三级缓存中获取到 A 的早期对象进入属性填充

循环依赖的三级缓存:

//一级缓存:存放所有初始化完成单实例 bean,单例池,key是beanName,value是对应的单实例对象引用
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//二级缓存:存放实例化未进行初始化的 Bean,提前引用池
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Cache of singleton factories: bean name to ObjectFactory. 3*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • 为什么需要三级缓存?

    • 循环依赖解决需要提前引用动态代理对象,AOP 动态代理是在 Bean 初始化后的后置处理中进行,这时的 bean 已经是成品对象。因为需要提前进行动态代理,三级缓存的 ObjectFactory 提前产生需要代理的对象,把提前引用放入二级缓存
    • 如果只有二级缓存,提前引用就直接放入了一级缓存,然后 Bean 初始化完成后又会放入一级缓存,产生数据覆盖,导致提前引用的对象和一级缓存中的并不是同一个对象
    • 一级缓存只能存放完整的单实例,为了保证 Bean 的生命周期不被破坏,不能将未初始化的 Bean 暴露到一级缓存
    • 若存在循环依赖,后置处理不创建代理对象,真正创建代理对象的过程是在 getBean(B) 的阶段中
  • 三级缓存一定会创建提前引用吗?

    • 出现循环依赖就会去三级缓存获取提前引用,不出现就不会,走正常的逻辑,创建完成直接放入一级缓存
    • 存在循环依赖,就创建代理对象放入二级缓存,如果没有增强方法就返回 createBeanInstance 创建的实例,因为 addSingletonFactory 参数中传入了实例化的 Bean,在 singletonFactory.getObject() 中返回给 singletonObject,所以存在循环依赖就一定会使用工厂,但是不一定创建的是代理对象,不需要增强就是原始对象
  • wrapIfNecessary 一定创建代理对象吗?(AOP 动态代理部分有源码解析)

    • 存在增强器会创建动态代理,不需要增强就不需要创建动态代理对象
    • 存在循环依赖会提前增强,初始化后不需要增强
  • 什么时候将 Bean 的引用提前暴露给第三级缓存的 ObjectFactory 持有?

    • 实例化之后,依赖注入之前

      createBeanInstance -> addSingletonFactory -> populateBean
      

源码解析

假如 A 依赖 B,B 依赖 A

  • 当 A 创建实例后填充属性前,执行:

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
    
    // 添加给定的单例工厂以构建指定的单例
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            // 单例池包含该Bean说明已经创建完成,不需要循环依赖
            if (!this.singletonObjects.containsKey(beanName)) {
                //加入三级缓存
                this.singletonFactories.put(beanName,singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                // 从二级缓存移除,因为三个Map中都是一个对象,不能同时存在!
                this.registeredSingletons.add(beanName);
            }
        }
    }
    
  • 填充属性时 A 依赖 B,这时需要 getBean(B),也会把 B 的工厂放入三级缓存,接着 B 填充属性时发现依赖 A,去进行**第一次 ** getSingleton(A)

    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);//为true代表允许拿到早期引用。
    }
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 在一级缓存中获取 beanName 对应的单实例对象。
        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) {
                        // 从三级缓存获取工厂对象,并得到 bean 的提前引用
                        singletonObject = singletonFactory.getObject();
                        // 【缓存升级】,放入二级缓存,提前引用池
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        // 从三级缓存移除该对象
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    
  • 从三级缓存获取 A 的 Bean:singletonFactory.getObject(),调用了 lambda 表达式的 getEarlyBeanReference 方法:

    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 【向提前引用代理池 earlyProxyReferences 中添加该 Bean,防止对象被重新代理】
        this.earlyProxyReferences.put(cacheKey, bean);
        // 创建代理对象,createProxy
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    
  • B 填充了 A 的提前引用后会继续初始化直到完成,返回原始 A 的逻辑继续执行

本文正在参加「金石计划 . 瓜分6万现金大奖」