spring源码解析:spring的循环依赖以及如何解决循环依赖


spring循环依赖简而言之就是多个bean之间存在相互依赖关系,跟Java语言中对象间的相互依赖本质是一样,可以把spring的bean看成Java中的对象,两个对象相互依赖,在Java中是可以的,但是在某些情况是不可行,下面举例看看哪些可以,哪些不行:

public class A {
    private B b;
}


public class B {
    private A a;
}

上面两个类A,B存在相互依赖,那么在创建对象的时候不管先创建谁,都可以给这两个字段提供setter方法,等两个对象创建完毕,再用setter方法进行赋值。这样就可以让两个对象相互依赖,如下:

   public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.setB(b);
        b.setA(a);
    }

那么要是两个类没有setter方法,而是通过构造方法给字段赋值的呢?

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }
}

这样的话就没办法将两个对象先创建出来再进行赋值,因为在创建对象的时候就需要依赖对方,没有对方的对象直接就编译报错,如下:

public static void main(String[] args) {
    A a = new A(); //编译报错,需要B对象,尝试下面方法呢?

    A a1 = new A(new B()); //同样编译报错,B对象创建又需要A对象

}

所以通过上面简单的例子,就可以知道,通过构造函数注入属性的循环依赖是没有办法解决的,因为Java语言中编译就报错了,spring也如此,无法处理构造方法的循环依赖注入(如果两个类还有有默认的构造方法,那么通过反射其实也可以完成,但是这也就违背了原则,而且spring也没法保证这个类就一定有默认构造)。

Spring循环依赖的三种方式

通过上面的demo就可以知道spring的循环依赖已有两种,setter依赖,和构造方法依赖,其实setter方法依赖需要再细分,就是单例setter循环依赖和多例的循环依赖。

单例的setter循环依赖是可以解决的,因为创建的对象会一直存在,两个对象也可以一直相互引用。而多例的setter循环依赖无法解决,因为多例对象用的时候才创建,用完就销毁,这样双方并不知道对方存在的具体时间,有可能A在,B不在了,那么通过A对象获取字段B,而B已经不在了,那么就会有问题。

通过原生简单的Java代码分析了循环依赖,spring也是按照这种大体的设计逻辑,进行编码的。结论就是:

  1. 单例setter循环依赖可以解决
  2. 多例setter循环依赖无法解决
  3. 构造函数循环依赖无法解决
  4. @DependsOn无法解决
  5. 单例的代理对象有可能解决

spring是如何解决循环的

所谓的spring是如何解决循环依赖,就是解决单例对象间通过setter方法注入的循环依赖,这种依赖在原生的Java编码是可行,spring也需要保证这一行为。

先给出结论,再结合源码分析。spring的具体做法就是:

  • 将创建的对象在不同时机放入到不同的缓存,一共三个,称三级缓存。
  1. 一级缓存-singletonObjects 存放创建完成的单例bean
  2. 二级缓存:earlySingletonObjects 见名知意,早产的单例对象,即存放不完整还没创建完成的单例
  3. 三级缓存:singletonFactories 这个缓存主要是用于解决有可能生成代理的单例对象,没有代理则返回原生对象,即AOP

步骤:

  • 创建对象A时先创建一个空对象,将bean的相关信息包装成一个ObjectFactory放入‘早期暴露’工厂缓存(三级缓存),有aop时会调用这个工厂方返回相应的代理对象
  • 然后再注入属性B,那么到容器中查找先找一级,二级,三级缓存,此时都没有找不到,就创建B,同样的放入三级缓存。
  • 创建B时发现需要A,那么就找A,按照缓存顺序查找,一二级没有,在三级中找到,那么此时需要拿到A,这里需要注意的时,如果A有aop则返回的是代理对象,如果没有则返回A对象本身,在返回A(可能是代理也可能是对象本身)的同时,把A放入到二级缓存,删除三级缓存的中的A工厂
  • B对象拿到A时,属性注入完成,后续其他操作完成时,删除二三级缓存中的B,放入一级缓存,那么B创建完成,此时A就可以注入B了
  • 然后A完成后续操作,再次从二级缓存获取(先走一级,找不到)到A把引用给A的变量,此时才是完整的A对象(B创建完成也存在这一步)
  • 如有其他的依赖循环执行

源码解析

有了上面的大概逻辑之后,再来结合源码分析下整个流程,以及为什么需要用到三级缓存。

首先spring的逻辑是不管是什么bean,先获取,获取不到就创建。假如是A对象(A1),调用getBean再调用doGetBean(),找三个缓存

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

这时找不到,就开始创建对象

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
    beforeSingletonCreation(beanName); //将bean加入到正常创建的集合
...
}

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
...
    instanceWrapper = createBeanInstance(beanName, mbd, args); //创建对象实例
...
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    /*
                    * 这里会执行spring的三个后置处理器:
                    *     AutowiredAnnotationBeanPostProcessor 获取当前bean是否有使用@Autowride,@Value注解标注的字段,并记录,方便后面注入
                    *     CommonAnnotationBeanPostProcessor
                    *     ApplicationListenerDetector 什么也没干
                    * */
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Post-processing of merged bean definition failed", ex);
                }
                mbd.postProcessed = true; //表示已经执行过了
            }
        }
...


        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {//如果能进入到这个if那么就说明,当前的spring容器是支持单例bean的循环依赖,而且这个bean已经在创建,那么加入到三级缓存
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
            /*
            * 放入常说的’三级缓存‘
            *
            * */
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    //如果有aop时,这里会有一个aop的smartInstantiationAware后置处理器的实现类,来创建一个代理对象
                    //没有则直接返回原对象
                    return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
}

这里是在创建实例A1完成之后就放入了三级缓存,放入的是个lamda表达式,为什么要放lamda表达式?继续往下看

    populateBean(beanName, mbd, instanceWrapper);

if (hasInstAwareBpps) {
            if (pvs == null) {//这里再次调用getPropertyValues()是为了给个默认值
                pvs = mbd.getPropertyValues();
            }
            for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
                PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    if (filteredPds == null) {
                        filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                    }
                    pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        return;
                    }
                }
                pvs = pvsToUse;
            }
        }

在这里会执行属性注入,那么获取spring的容器中查找该bean,如果需要B,那么又会调用上面的getSingleton方法,获取B,同样的放入正在创建的集合,创建完成放入三级缓存,继续调用populateBean注入A,需要A时再次从三个缓存中查找,这时三级缓存中是有A的,那么此时就会调用存入的lamda表达式获取A对象(这里获取到的称为A2),即调用lamda表达式的getEarlyBeanReference方法,如下:

    addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
                }
            });


    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            //这里如果有AOp那么 会有一个AbstractAutoProxyCreator的实例会执行,并创建一个代理对象
            for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
        return exposedObject;
    }

在调用getEarlyBeanReference方法时,会执行smartInstantiationAware这个集合,并增强bean,这里通常返回的是原来的bean,而有AOP代理时,这里返回的就是增强的bean。这就是为什么三级缓存存入的是一个lamda表达式,通过lamda的回调方法很巧妙的处理了不同的需求。

在获取到A时,就将三级缓存中的A2移动到二级缓存。为什么要移动到二级缓存?继续看

那么此时找到了A2,接着继续将A2注入给B的属性,如果有AOP的话,那此时原来正在创建的A1和注入给B的A2并不是同一个对象,B注入完成之后,会调用initializeBean方法中的bean的后置处理器。

    if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
        //这里如果有AOP会执行相关的方法
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

AbstractAutoProxyCreator也实现了这个接口,这是AOP的回调,此时如果有aop那么就会创建代理对象,所以当b执行完毕之后,有aop那么B2也是一个代理对象,不在是原来的B1对象,返回B,放入一级缓存。

此时B2已经创建完成了,不管他是代理对象还是原生对象都会返回给原来的B1的变量,注入给原来的A1。现在B中的A2不管是不是原来的A1都不重要,重要的是spring是如何保证原来的A1 和B中的A2是同一个对象的。

在A2注入完成B之后,同样会执行上面的bean的后置处理器方法,只不过再次执行相关AOP回调时,已经创建过了,就不会再创建。在最后还会调用

if (earlySingletonExposure) {
            Object earlySingletonReference = getSingleton(beanName, false);//缓存中的bean可能时代理对象
            if (earlySingletonReference != null) {
                if (exposedObject == bean) { //初始化之后的bean和实例化的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 " +
                                "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }
        }

在这里会再次从缓存中获取A2,他还在二级缓存中,也是B中引用的A2,将这个A2赋值给原来的A1,保证了统一。

那再看看这里如果没有移动到二级缓存,而是三级缓存,那么此时如果从三级缓存获取,则会创建再次创建一个代理对象,就是A3,那么后面就没法控制A的一致性

为什么需要三级缓存解决循环依赖

按照我们习惯的开发思维,越简单实现就越好,不用缓存行不行,那用一级缓存行不行,不行的话用两个缓存行不行?

  • 首先不用缓存肯定不行,因为需要有地方存放已经创建好的单例bean。
  • 那用一个缓存行不行?
    如果只是单单的从解决bean的相互依赖的问题出发,不考虑其他,其实也是可以解决部分的依赖的。假如都是setter注入的bean,A创建好就放入缓存,注入B,发现B没有,创建B放入缓存,再注入A,从缓存里找可以找到,注入给B,B完成创建,A注入B,这样就可以解决了,但是spring考虑的不单单是依赖注入,还有其他的扩展,如AOP。那么将AOP提前到放入缓存之前呢?这也可以的,但是问题是只有一个缓存的话,那么用户获取的时候就可能获取到的不是一个完整的bean,可能是正在创建的bean

  • 用两个缓存行不行?
    跟上面一样,如果只考虑循环依赖的结果,或者某种结果,也是可以,只不过spring是一个庞大的体系,循环依赖只是bean生命周期的冰上一角,spring要考虑不单单是循环依赖,还有其他的很多东西。从设计和使用,效率的角度来看,在庞大的spring体系中,可能使用三级缓存是他目前最好的实现方式吧


文章作者: Needle
转载声明:本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Needle !
  目录
  评论