什么是循环依赖?Spring是怎么解决循环依赖的?

一、什么是循环依赖?

我们来看Spring官网文档对这个是怎么解释的:

链接放在这里了:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

简单来说,A对象有一个属性是B,B对象有一个属性是A,当A对象生成的时候依赖B对象,B对象生成的时候需要依赖A对象,这个时候就很容易形成一个闭环,如果这样一直死循环下去。

与此同时,我们从文档中得知,AB循环依赖只要A的注入方式是setter且是Singleton,就不会出现环循环以来的问题。即我们通过setter注入的方式,来间接实现循环依赖的破解。

重要!!!!!!:通常来说,如果问Spring是怎么解决循环依赖的,一定是指默认的单例Bean中,属性相互引用的场景。Prototype的场景不支持循环依赖,会报错。

二、循环依赖的解决

重要:Spring内部通过三级缓存来解决循环依赖的问题。核心类:DefaultSingletonBeanRegistry

所谓三级缓存,就是spring容器解决循环依赖的三个map:

一级缓存:singletonObjects,是一个ConcurrentHashMap,也叫单例池,存放已经经历了完整生命周期的Bean对象;

二级缓存:earlySingletonObjects,是一个HashMap,存放早期暴露出来的Bean对象,Bean的生命周期未结束,属性还未填充完整;

三级缓存:singletonFactories,第三级缓存value是一个工厂,三级缓存存放可以生成bean的工厂。

只有单例的Bean会通过三级缓存来解决循环依赖的问题,而非单例的Bean,每次从容器获取都是一个新的对象,都会重新创建,所以非单例的Bean是没有缓存的,不会放在三级缓存中。

2.1 理论准备

(1)实例化和初始化

  • 实例化:内存中申请一块内存空间
  • 初始化:属性填充,完成属性的各种赋值

二级缓存就是已经实例化但是未初始化的对象。

(2)关注三个map和四个方法

 (3)AB对象再三级缓存中的迁移说明

  • A创建过程需要B,于是A先将自己放入到三级缓存中,去实例化B
  • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,就查三级缓存,找到A,然后把三级缓存里面的A放到二级缓存,并删除三级缓存里面的A
  • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态),然后回来继续创建A,此时B已经创建结束,直接从一级缓冲中拿到B,并完成创建,然后A把自己放到一级缓存中去。

2.2 源码分析

2.2.1 demo示例

为了方便断点分析这个过程,给出如下一个简单demo:

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean name="a" class="circulardependency.ClassA" scope="singleton">
        <property name="b" ref="b"></property>
    </bean>

    <bean name="b" class="circulardependency.ClassB" scope="singleton">
        <property name="a" ref="a"></property>
    </bean>

</beans>

ClassA  ClassB  Main

/**
 * @Author leijs
 * @date 2021/8/23
 */
@Data
public class ClassA {

    private ClassB b;

    public ClassA() {
        System.out.println("A 初始化完成");
    }
}
=========================================================================================

/**
 * @Author leijs
 * @date 2021/8/23
 */
@Data
public class ClassB {

    private ClassA a;

    public ClassB() {
        System.out.println("B 初始化完成");
    }

}
========================================================================================== import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author leijs * @date 2021/8/23 */ public class CircularMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); ClassA classA = context.getBean("a", ClassA.class); ClassB classB = context.getBean("b", ClassB.class); } }

2.2.2 断点过程

refresh:这个方法,Spring加载容器初始化的方法

 进入refresh的方法:需要关注的:finishBeanFactoryInitialization(beanFactory)

 进入方法:protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)

也就是DefaultListableBeanFactory#preInstantiateSingletons的来处理。

    @Override
    public void preInstantiateSingletons() throws BeansException {
        if (logger.isTraceEnabled()) {
            logger.trace("Pre-instantiating singletons in " + this);
        }

        // Iterate over a copy to allow for init methods which in turn register new bean definitions.
        // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
        List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                    if (bean instanceof FactoryBean) {
                        final FactoryBean<?> factory = (FactoryBean<?>) bean;
                        boolean isEagerInit;
                        if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                            ((SmartFactoryBean<?>) factory)::isEagerInit,
                                    getAccessControlContext());
                        }
                        else {
                            isEagerInit = (factory instanceof SmartFactoryBean &&
                                    ((SmartFactoryBean<?>) factory).isEagerInit());
                        }
                        if (isEagerInit) {
                            getBean(beanName);
                        }
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

        // Trigger post-initialization callback for all applicable beans...
        for (String beanName : beanNames) {
            Object singletonInstance = getSingleton(beanName);
            if (singletonInstance instanceof SmartInitializingSingleton) {
                final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                        smartSingleton.afterSingletonsInstantiated();
                        return null;
                    }, getAccessControlContext());
                }
                else {
                    smartSingleton.afterSingletonsInstantiated();
                }
            }
        }
    }

首先我们要初始化两个Bean出来:

其次来到getBean方法

2.2.2.1 doGetBean

关注这里的doGetBean方法(spring以do开头的方法一般都是具体的实现逻辑)

来看下这里获取单例的方法:

@Nullable
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }

    /**
     * Return the (raw) singleton object registered under the given name.
     * <p>Checks already instantiated singletons and also allows for an early
     * reference to a currently created singleton (resolving a circular reference).
     * @param beanName the name of the bean to look for
     * @param allowEarlyReference whether early references should be created or not
     * @return the registered singleton object, or {@code null} if none found
     */
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        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) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

可以看到是否允许提前暴露对象是true,但是第一次a进来的时候,从单例对象池singletonObjects获取,是拿不到的,所以最后retrurn null;  

接下来,标记a正在创建:

核心进入Bean的创建:这里我们肯定是一个单例的Bean.

 createBean -->  doCreateBean

 我们先来看getSingleton这个方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    // Has the singleton object implicitly appeared in the meantime ->
                    // if yes, proceed with it since the exception indicates that state.
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

关注singletonObject = singletonFactory.getObject(); 因为我们这个时候从容器中拿不到“a”这个Bean,通过回调来创建,这里就会来到我们的createBean(),再贴一下刚才这个地方:

        mbdToUse.prepareMethodOverrides();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
                    beanName, "Validation of method overrides failed", ex);
        }

        try {
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            if (bean != null) {
                return bean;
            }
        }
        catch (Throwable ex) {
            throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                    "BeanPostProcessor before instantiation of bean failed", ex);
        }

        try {
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            if (logger.isTraceEnabled()) {
                logger.trace("Finished creating instance of bean '" + beanName + "'");
            }
            return beanInstance;
        }
        catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
            // A previously detected exception with proper bean creation context already,
            // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
        }
    }

 

 

 看这里的addSingletonFactory方法,首先会看一级缓存有没有这个bean, 此时是没有A的,然后把A加入到三级缓存;然后把A从二级缓存中删除,此时这个删除是空删,因为二级缓存也没有A。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

2.2.2.2 属性填充--populateBean

到前面,我们生成了A,

我们A依赖B,

 

然后我们来处理B:

 

    @Nullable
    private Object resolveReference(Object argName, RuntimeBeanReference ref) {
        try {
            Object bean;
            String refName = ref.getBeanName();
            refName = String.valueOf(doEvaluate(refName));
            if (ref.isToParent()) {
                if (this.beanFactory.getParentBeanFactory() == null) {
                    throw new BeanCreationException(
                            this.beanDefinition.getResourceDescription(), this.beanName,
                            "Can't resolve reference to bean '" + refName +
                            "' in parent factory: no parent factory available");
                }
                bean = this.beanFactory.getParentBeanFactory().getBean(refName);
            }
            else {
                bean = this.beanFactory.getBean(refName);
                this.beanFactory.registerDependentBean(refName, this.beanName);
            }
            if (bean instanceof NullBean) {
                bean = null;
            }
            return bean;
        }
        catch (BeansException ex) {
            throw new BeanCreationException(
                    this.beanDefinition.getResourceDescription(), this.beanName,
                    "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
        }
    }

看红色getBean, 又是doGetBean,和前面创造A类似创建B。从一级缓存中拿不到B,这个通过工厂创建B

相当于此时B和A都已经通过Bean工厂生成了。此时B发现也需要A。然后populateBean, 同样到resolveReference -->  this.beanFactory.getBean -->  getSingleton(beanName)

此时A在单例池中没有,但是是正在创建中,所以进来这个if判断。进一步分析,从二级缓存中拿A,此时二级缓存中没有A,从三级缓存中拿A,此时三级缓存是有的。

然后把A放到二级缓存,并从三级缓存中删除。相当于通过三级缓存中lambda表达式建造成的A放到了二级缓存。

 有了A,B就可以正常完成属性的填充,

2.2.2.3 initialiezeBean

可以看到此时的B的A属性是已经填充好了。

接下来:AbstractBeanFactory # registerDisposableBeanIfNecessary  -->  DefaultSingletonBeanRegistry# addSingleton

也就是把B加入到了一级缓存,单例池,并从二级和三级缓存移除。

 接下来,我们继续处理A

同样类似步骤,A从一级缓存可以拿到B,初始化完成后,A也加入一级缓存,并从三级缓存和二级缓存中删除。后面的步骤就不重要了。

三、其他问题

3.1 为什么需要三级缓存?

上面的断点过程是一个不包含AOP的简单过程。在Bean的整个生命周期中,有很多的BeanPostProcessor,我们可以Bean进行AOP的代理产生新的代理对象。

顺带贴一张Bean生命周期的图:

当拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

getEarlyBeanReference这个方法主要逻辑大概描述下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例,这时我们会发现能够拿到bean实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。下面是重点,我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???

只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景

我们发现A被AOP代理了,getEarlyBeanReference返回的是什么对象返回的是一个A的代理对象,还是被CGLIB代理的

再执行一遍singletonFactory.getObject()返回的是否是同一个A的代理对象,并不是。singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为A是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为A是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。

所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。

四、总结

Spring创建Bean对象主要分为两个步骤:

(1)创建原始Bean对象

(2)填充对象属性和初始化

每次创建Bean的之前,都会从缓存中查下有没有bean,因为是单例,只能有一个;当我们创建完成BeanA 的原始对象后,并把他放到三级缓存中,这时候发现依赖B,接着去创建Bean B , 同样的流程填充时发现依赖bean A又是同样的流程。不同的是,这个时候可以在三级缓存中查到刚放进去的原始对象beanA, 所以不需要继续创建,用它来注入Bean  B , 完成B的创建。 既然B创建好了,所以A就可以继续完成填充属性的步骤了,闭环完成。

以上。

 

 

 

posted @ 2021-08-23 22:27  未知的九月  阅读(2314)  评论(0编辑  收藏  举报