Spring如何解决循环依赖?

springboot实战电商我的项目mall4j (https://gitee.com/gz-yami/mall4j)

java开源商城零碎

@componentclass A {    private B b;}@componentclass B {    private A a;}

类A依赖了B作为属性,类B又应用类A作为属性,彼此循环依赖。

源码了解:

//调用AbstractBeanFactory.doGetBean(),向IOC容器获取Bean,触发依赖注入的办法protected <T> T doGetBean(            String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)            throws BeansException {        ...         // 第一次getSingleton获取对象实例        // 先从缓存中取是否曾经有被创立过的单例类型的Bean[没有的话就去获取半成品的,也就是earlySingletonObjects,缓存二的货色]        // 对于单例模式的Bean整个IOC容器中只创立一次,不须要反复创立        Object sharedInstance = getSingleton(beanName);        ...        try {            //创立单例模式Bean的实例对象            if (mbd.isSingleton()) {                //第二次getSingleton尝试创立指标对象,并且注入属性                //这里应用了一个匿名外部类,创立Bean实例对象,并且注册给所依赖的对象                sharedInstance = getSingleton(beanName, () -> {                    try {                        //创立一个指定Bean实例对象,如果有父级继承,则合并子类和父类的定义                        return createBean(beanName, mbd, args);                    } catch (BeansException ex) {                        //显式地从容器单例模式Bean缓存中革除实例对象                        destroySingleton(beanName);                        throw ex;                    }                });                // 如果传入的是factoryBean,则会调用其getObject办法,失去指标对象                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);                //IOC容器创立原型模式Bean实例对象            }            ...        } catch (BeansException ex) {            cleanupAfterBeanCreationFailure(beanName);            throw ex;        }        ...        return (T) bean;    }

也就是实例化A的时候在缓存中没找到[第一个getSingleton],就去第二个getSingleton实例化A[实际上是调用了doCreateBean()],因为A须要B,又去doGetBean尝试获取B,发现B也不在缓存中,持续调用第二个getSingleton去实例化,当要注入属性A的时候在二级缓存找到了半成品A,胜利注入返回到A实例化的阶段,将B注入。

第一个getSingleton代码

    @Nullable    protected Object getSingleton(String beanName, boolean allowEarlyReference) {        //从spring容器中获取bean        Object singletonObject = this.singletonObjects.get(beanName);//缓存1        //如果获取不到 判断要获取的对象是不是正在创立过程中----如果是,则去缓存(三级缓存)中取对象(不是bean)        //isSingletonCurrentlyInCreation() 寄存的对象 的机会是在getBean中第二次调用getSingleton时候beforeSingletonCreation(beanName);存进去的        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {            synchronized (this.singletonObjects) {                singletonObject = this.earlySingletonObjects.get(beanName);//缓存2                if (singletonObject == null && allowEarlyReference) {//allowEarlyReference--判断是否反对循环依赖,默认为true                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//缓存3                    if (singletonFactory != null) {                        singletonObject = singletonFactory.getObject();                        //将三级缓存降级为二级缓存                        this.earlySingletonObjects.put(beanName, singletonObject);                        //从三级缓存中删除  为什么删除?避免反复创立。设置三级缓存的目标是为了进步性能,因为每次创立都须要通过factory,会破费很多工夫                        this.singletonFactories.remove(beanName);                    }                }            }        }        return singletonObject;    }

在实例化AB的时候,三个缓存都是找不到这两个类的,因为两者均未创立;

三级缓存

    /** Cache of singleton objects: bean name --> bean instance */    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//一级缓存,寄存残缺的bean信息    /** Cache of singleton factories: bean name --> ObjectFactory */    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//三级缓存,bean创立完了就放进去,还有他的bean工厂    /** Cache of early singleton objects: bean name --> bean instance */    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//二级缓存,寄存还没进行属性赋值的bean对象,也即半成品bean

第二个getSingleton代码

此处将A先创立好<u>放入三级缓存</u>中,实际上是委托给另一个doGetBean()实现的

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {        ...        synchronized (this.singletonObjects) {            // 再次判断ioc容器中有无该bean            Object singletonObject = this.singletonObjects.get(beanName);            if (singletonObject == null) {                ..before..                try {                    // 回调到doCreateBean                    singletonObject = singletonFactory.getObject();                    newSingleton = true;                }                ..after..            }            return singletonObject;        }    }

因为第一个获取单例的办法找不到AB,故此将会进入第二个获取单例的办法试图找到,这个办法里singletonFactory.getObject()为外围,将会回调到doCreateBean办法持续创立Bean。

doCreateBean代码

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)            throws BeanCreationException {        //封装被创立的Bean对象        BeanWrapper instanceWrapper = null;        if (mbd.isSingleton()) {            //单例的状况下尝试从factoryBeanInstanceCache获取 instanceWrapper,并革除同名缓存            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);        }        if (instanceWrapper == null) {            // 创立Bean实例            // instanceWrapper会包装好指标对象            instanceWrapper = createBeanInstance(beanName, mbd, args);        }        ...        //向容器中缓存单例模式的Bean对象,以防循环援用,allowCircularReferences是判断是否反对循环依赖,这个值能够改为false        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&                isSingletonCurrentlyInCreation(beanName));        //判断是否容许循环依赖        if (earlySingletonExposure) {            ...            //将这个对象的工厂放入缓存中  (注册bean工厂singletonFactories,第一个getSingleton应用的),此时这个bean还没做属性注入            //这里是一个匿名外部类,为了避免循环援用,尽早持有对象的援用            // getEarlyBeanReference很特地,这外面会做aop代理            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));        }        //Bean对象的初始化,依赖注入在此触发        //这个exposedObject在初始化实现之后返回作为依赖注入实现后的Bean        Object exposedObject = bean;        try {            //填充属性 -- 主动注入            //后面是实例化,并没有设置值,这里是设置值.将Bean实例对象封装,并且Bean定义中配置的属性值赋值给实例对象                //这里做注入的时候会判断依赖的属性在不在,不在就调用doGetBean持续创立            populateBean(beanName, mbd, instanceWrapper);            // 该办法次要是对bean做一些扩大            // 初始化Bean对象。属性注入已实现,解决各种回调            // (对实现Aware接口(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)的bean执行回调、            // aop、init-method、destroy-method?、InitializingBean、DisposableBean等)            exposedObject = initializeBean(beanName, exposedObject, mbd);        }        ...        return exposedObject;    }

在这个办法里,实例化A的时候,曾经把A作为一个半成品通过调用addSingletonFactory办法将其退出了三级缓存singletonFactories,不便在递归实例化B的时候能够获取到A的半成品实例,具体代码如下:

将创立的bean退出<u>三级缓存</u>

产生在addSingletonFactory这个办法

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {        Assert.notNull(singletonFactory, "Singleton factory must not be null");        synchronized (this.singletonObjects) {            if (!this.singletonObjects.containsKey(beanName)) {                //放到三级缓存                // 注册bean工厂                this.singletonFactories.put(beanName, singletonFactory);                this.earlySingletonObjects.remove(beanName);                this.registeredSingletons.add(beanName);            }        }    }

那么是在什么时候会产生一个递归的调用呢?

实际上是在<u>populateBean(beanName, mbd, instanceWrapper);</u>要做属性注入的时候,假如是依据名称主动注入的,调用<u>autowireByName(),</u>该法会去循环遍历在getBean之前曾经把xml文件的属性退出到注册表之类的属性,

populateBean有一个autowireByName的办法,代码如下

protected void autowireByName(            String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {        //对Bean对象中非简略属性(不是简略继承的对象,如8中原始类型,字符串,URL等都是简略属性)进行解决        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);        for (String propertyName : propertyNames) {            //如果Spring IOC容器中蕴含指定名称的Bean,就算还没进行初始化,A发现有B属性时,B属性曾经被写入注册表之类的货色,所以这个判断返回true            if (containsBean(propertyName)) {                //调用getBean办法向IOC容器索取指定名称的Bean实例,迭代触发属性的初始化和依赖注入                Object bean = getBean(propertyName); //实际上就是委托doGetBean()                pvs.add(propertyName, bean);                //指定名称属性注册依赖Bean名称,进行属性依赖注入                registerDependentBean(propertyName, beanName);                if (logger.isDebugEnabled()) {                    logger.debug("Added autowiring by name from bean name '" + beanName +                            "' via property '" + propertyName + "' to bean named '" + propertyName + "'");                }            }            ...        }    }

Object bean = getBean(propertyName); 这个办法实际上又去委托了doGetBean(),又一次递归的走上了流程,也就是A在实例化到该步时发现,还有一个B,就会又从doGetBean()开始,一步步的寻找创立,不同的是,当B走到这个依据名称注入的办法时,此时的曾经能在二级缓存里找到A的身影了,无需再次创立A对象。

总结

spring使用三级缓存解决了循环依赖的问题;

采纳递归的形式,逐渐的去实例化对象,并将上一步曾经退出缓存的半成品对象作为属性注入;

等到走到最初一个递归时,将会逐渐返回,把对应的实例一个个创立好。

springboot实战电商我的项目mall4j (https://gitee.com/gz-yami/mall4j)

java开源商城零碎