Spring专题之bean的循环依赖

简介

Spring在初始化容器过程中会对容器内的单例Bean对象进行初始化,而这初始化过程中存在对Bean的依赖注入。当存在A依赖B,B依赖C,C依赖A这种情况的时候,即出现了循环依赖的问题,这个时候Spring是如何来解决的呢?所以本文跟大家一起讨论Spring容器启动过程中对bean循环依赖的解决之道。
image.png

分析-入口

本文源码分析基于SpringFrameWork-5.1.5_release版本。因为spring的循环依赖问题是在容器初始化过程中对单例bean的初始化发生的,所以这里开始从spring对单例bean的初始化开始分析,进入org.springframework.context.support.AbstractApplicationContext类中找到refresh这个这个方法

org.springframework.context.support.AbstractApplicationContext#refresh

public void refresh() throws BeansException, IllegalStateException {
        ......
        try {
            .....
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var9) {
            ......
        } finally {
            ......
        }
    }
}

这段代码中的this.finishBeanFactoryInitialization(beanFactory)这行代码是spring bean初始化的入口,跟踪进入这个方法

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    ......
    beanFactory.preInstantiateSingletons();
}

可以看到preInstantiateSingletons这个方法,这个方法是spring开始正式对容器中的单例bean进行初始化并且依赖注入。

分析-过程

跟踪进入上面这个preInstantiateSingletons方法中,来到这个org.springframework.beans.factory.support.DefaultListableBeanFactory类,方法代码如下:

public void preInstantiateSingletons() throws BeansException {
    ......
    List<String> beanNames = new ArrayList(this.beanDefinitionNames);
    Iterator var2 = beanNames.iterator();
    while(true) {
        String beanName;
        Object bean;
        do {
            while(true) {
                RootBeanDefinition bd;
                do {
                    do {
                        do {
                            ......
                            beanName = (String)var2.next();
                            bd = this.getMergedLocalBeanDefinition(beanName);
                        } while(bd.isAbstract());
                    } while(!bd.isSingleton());
                } while(bd.isLazyInit());
                
                if (this.isFactoryBean(beanName)) {
                    bean = this.getBean("&" + beanName);
                    break;
                }
                this.getBean(beanName);
            }
        } while(!(bean instanceof FactoryBean));
        ......
    }
}

这里需要说明的地方是代码中存在的三个do while循环,spring想表达的逻辑是当bean定义是懒加载,不是单例或者是抽象bean的情况下,会循环下去。言外之意是当一个bean,既不是懒加载,并且是单例以及不是抽象的情况下,会跳出循环从而进入到getBean方法中进行bean的初始化。
跟踪进入getBean方法,最终会进入到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中,需要关注的代码如下:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = this.transformedBeanName(name);
    //关注点1,先尝试获取bean
    Object sharedInstance = this.getSingleton(beanName);
    Object bean;
    //关注点2,如果sharedInstance不为空,则直接返回。
    if (sharedInstance != null && args == null) {
        if (this.logger.isTraceEnabled()) {
            if (this.isSingletonCurrentlyInCreation(beanName)) {
                this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
                this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
    } else {
        //关注点3,这里的意思是当需要从Spring容器中获取Prototype类型的bean时,而这个bean处于正在创建的阶段时会直接抛出异常,言外之意即对于Prototype类型的bean,spring不支持循环依赖。
        if (this.isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        ......
        try {
            RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
            this.checkMergedBeanDefinition(mbd, beanName, args);
            ......

            if (mbd.isSingleton()) {
                //关注点4,单例bean的初始化入口
                sharedInstance = this.getSingleton(beanName, () -> {
                    try {
                        return this.createBean(beanName, mbd, args);
                    } catch (BeansException var5) {
                        this.destroySingleton(beanName);
                        throw var5;
                    }
                });
                bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            } 
            ......
        } catch (BeansException var26) {
            this.cleanupAfterBeanCreationFailure(beanName);
            throw var26;
        }
    }
    ......
}

从上面的标注的关注点1中可以看到,获取bean的时候会先尝试从spring容器中获取bean,进入关注1中的方法,最终可以跟踪进入到org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)中,代码如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //先从singletonObjects这个map缓存中获取已经初始化完毕的对象
    Object singletonObject = this.singletonObjects.get(beanName);
    //如果获取不到,要不就说明这个bean对象还没有开始创建,要不就处于正在创建阶段,比如,A依赖B,B依赖A,spring在初始化B对象的时候,会依赖注入,获取依赖A对象,而这个A对象正好处于正在创建阶段,所以会进入到这个判断
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        Map var4 = this.singletonObjects;
        synchronized(this.singletonObjects) {
            //earlySingletonObjects这个也是一个map缓存对象,预加载完成但没有进行依赖注入的bean对象会在这个里面
            singletonObject = this.earlySingletonObjects.get(beanName);
            //如果earlySingletonObjects里获取不到,并且允许获取bean早期引用
            if (singletonObject == null && allowEarlyReference) {
                //重点关注,singletonFactories这个也是一个key为beanName,value为ObjectFactory的map缓存对象(通过这个ObjectFactory的getObject方法可以获取早期应用对象)
                ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

代码逻辑需要关注的地方这里使用了注解说明,其中需要理解的点有几个,第一个是singletonObjects,earlySingletonObjects,singletonFactories这几个缓存对象的作用,第二个是singletonFactories这个对象的key,value是在什么时候在put填充的(后面说明)。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new HashMap(16);

从上面的源码中可以看出singletonObjects,singletonFactories,earlySingletonObjects都是map对象,分别用于缓存已经初始化完毕(包括依赖注入)的bean,bean对象的ObjectFactory对象(用于获取早期bean对象)以及早期应用bean对象(仅仅缓存作用)。
重新回到关注点4代码中,代码如下:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

if (mbd.isSingleton()) {
    sharedInstance = this.getSingleton(beanName, () -> {
        try {
            return this.createBean(beanName, mbd, args);
        } catch (BeansException var5) {
            this.destroySingleton(beanName);
            throw var5;
        }
    });
    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

跟踪进入org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)方法中,最终会触发this.createBean(beanName, mbd, args)的调用,进行bean的初始化,代码如下:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    Map var3 = this.singletonObjects;
    synchronized(this.singletonObjects) {
        //先从singletonObjects缓存中获取bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //如果当前获取的bean处于正在销毁的阶段,spring直接抛出异常,即无法获取正在处于销毁阶段的bean。
            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!)");
            }
            ......
            //这里是将beanName加入到singletonsCurrentlyInCreation缓存列表中,用于判断当前beanName是否处于初始化阶段
            this.beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            ......
            try {
                //调用方法参数singletonFactory的getObject,即触发createBean方法初始化bean对象
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } catch (IllegalStateException var16) {
                ......
            } catch (BeanCreationException var17) {
                ......
            } finally {
                ......
                //从singletonsCurrentlyInCreation缓存列表中移除当前创建的bean,表明该bean对象已经不处于正在初始化阶段
                this.afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //将成功初始化的bean对象加入singletonObjects缓存map中,并且singletonFactories,earlySingletonObjects对象中移除对应beanName的早期引用获取方法,以及早期引用。
                this.addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

上面的源码逻辑从注解中可以看出,其中创建bean的操作会进入createBean方法,最终可以跟踪进入org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法中,代码如下:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
    ......
    //bean实例化
    if (instanceWrapper == null) {
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }

    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    ......
    //bean是单例的,并且允许循环依赖以及当前bean处于正在创建的阶段
    boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
    if (earlySingletonExposure) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
        }
        //重点关注,将早期引用获取的ObjectFactory类put进入singletonFactories缓存Map对象中
        this.addSingletonFactory(beanName, () -> {
            return this.getEarlyBeanReference(beanName, mbd, bean);
        });
    }
    Object exposedObject = bean;
    try {
        //依赖注入核心
        this.populateBean(beanName, mbd, instanceWrapper);
        exposedObject = this.initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable var18) {
        ......
    }
    ......
    try {
        ......        
        return exposedObject;
    } catch (BeanDefinitionValidationException var16) {
        ......
    }
}

至此,Spring通过对singletonFactories,earlySingletonObjects,singletonObjects几个缓存对象的使用,解决了单例bean循环依赖的问题。需要注意的是,这里的依赖指的是属性依赖,如果是构造器依赖或者是Prototype类型的bean,spring是不支持的,原因主要是spring通过构造器实例化bean对象的时候,还未调用addSingletonFactory这个方法,所以无法提前获取到依赖bena的预初始化的bean对象,而Prototype类型的bean在获取依赖的时候会判断是否处于正在创建阶段,如果是会直接抛出异常。所以总结来说Spring在5.1.5这个版本中仅支持单例bean的循环依赖,其他版本可以参考本文源码分析自行了解。

分析-总结

总结下,在Spring中,单例A依赖B,B依赖A中,首先Spring先进入A的创建,在通过构造方法实例化后,因为bean是单例的,并且允许循环依赖以及当前bean处于正在创建的阶段,所以spring会将早期引用获取的ObjectFactory类put进入singletonFactories缓存Map对象中,后面开始
populateBean进行依赖注入,会解析得到A的依赖B,从而进入B对象的初始化,当进行B对象的初始化过程中,同样也会进行B对象的依赖注入,会解析得到B的依赖A,从而进入A对象的获取即doGetBean操作,doGetBean中会先调用getSingleton(org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean))方法尝试获取初始化完毕的bena对象或者是预初始化的bean对象,从而最终B获取到了预初始化的A对象(已经实例化但还没进行依赖注入)。

所以平时代码中如果在bean创建出现BeanCurrentlyInCreationException或者BeanCreationException异常时,可以先考虑下是否bean依赖出现循环依赖问题,结合本文的内容进行排查。


程序猿Grand的程序人生
写简单的代码,学习不简单的思想,做了不起的事情。

念念不忘,终有回响。

64 声望
23 粉丝
0 条评论
推荐阅读
Java12的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft63阅读 12.6k

Java8的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft32阅读 27.3k评论 1

一文彻底搞懂加密、数字签名和数字证书!
微信搜索🔍「编程指北」,关注这个写干货的程序员,回复「资源」,即可获取后台开发学习路线和书籍来源:个人CS学习网站:[链接]前言这本是 2020 年一个平平无奇的周末,小北在家里刷着 B 站,看着喜欢的 up 主视...

编程指北71阅读 33.3k评论 20

Java11的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft28阅读 19.2k评论 3

Java5的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft13阅读 21.7k

Java9的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft20阅读 15.2k

Java13的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft17阅读 11.1k

念念不忘,终有回响。

64 声望
23 粉丝
宣传栏