Spring - Bean 循环依赖问题

一、准备工作
下面新建一个Maven工程的Web项目,其中有两个实体类分别如下:

package com.egov.pojo;

/**
 * Created by wuguoping on 2017/9/9 Desc:
 */
public class ClassA {

    private ClassB classB;

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public ClassB getClassB() {
        return classB;
    }

    public ClassA(){

    }
    public ClassA(ClassB classB){
        this.classB = classB;
    }
}

package com.egov.pojo;

import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by wuguoping on 2017/9/9 Desc:
 */
public class ClassB {

    private ClassA classA;

    public void setClassA(ClassA classA) {
        this.classA = classA;
    }

    public ClassA getClassA() {
        return classA;
    }

    public ClassB(){

    }

    public ClassB(ClassA classA){
        this.classA = classA;
    }
}

Bean在Spring的配置文件applicationContext.xml中的配置在后续具体分析是给出。
测试类如下:

public class Main {
    public static void main(String[] args){
        ApplicationContext applicationContext = new
            ClassPathXmlApplicationContext("classpath:config/applicationContext.xml");
    }
}

二、先看现象
1、构造器注入,两者为单例模式---报错
其中在Spring 的配置文件applicationContext.xml对两个个类的定义如下:

   <bean id="classA" class="com.egov.pojo.ClassA" scope="singleton">
      <constructor-arg index="0" ref="classB" />
   </bean>

   <bean id="classB" class="com.egov.pojo.ClassB" scope="singleton">
      <constructor-arg index="0" ref="classA" />
   </bean>

执行结果:

警告: Exception encountered during context initialization - cancelling refresh attempt
org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'classA' defined in class path resource [config/applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'classB' defined in class path resource [config/applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: 

Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?

2、构造器注入,两者为原型对象---报错

public class Main {
    public static void main(String[] args){
        ApplicationContext applicationContext = new
            ClassPathXmlApplicationContext("classpath:config/applicationContext.xml");
        //在此需要请求触发
        System.out.print(applicationContext.getBean("classA", ClassA.class));
    }
}

其中在Spring 的配置文件applicationContext.xml对两个个类的定义如下:

   <bean id="classA" class="com.egov.pojo.ClassA" scope="prototype">
      <constructor-arg index="0" ref="classB" />
   </bean>

   <bean id="classB" class="com.egov.pojo.ClassB" scope="prototype">
      <constructor-arg index="0" ref="classA" />
   </bean>

执行结果:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'classA' defined in class path resource [config/applicationContext.xml]: Cannot resolve reference to bean 'classB' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'classB' defined in class path resource [config/applicationContext.xml]: Cannot resolve reference to bean 'classA' while setting constructor argument; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation:

 Is there an unresolvable circular reference?

3、构造器注入,A为单例,B为原型--报错
4、构造器注入,A为原型,B为单例--报错
上述两种情况没必要再贴出来,后面会分析,这是必然情况。

5、属性注入(Setting),两者都为单例--成功

<bean id="classA" class="com.egov.pojo.ClassA" scope="singleton">
      <property name="classB" ref="classB" />
   </bean>
   <bean id="classB" class="com.egov.pojo.ClassB" scope="singleton">
      <property name="classA" ref="classA" />
   </bean>
信息: Loading XML bean definitions from class path resource [config/applicationContext.xml]
com.egov.pojo.ClassA@dc24521
Process finished with exit code 0

6、属性注入,A为单例,B为原型--成功
7、属性注入,A为原型,B为单例--
报错
8、属性注入,两者都为原型---报错
...
错误原因一样,就不贴了,偷偷懒

三、原因分析
现象背后必然有原因,接下来就用源码来论证上述结论。首先,不难看出,上面一共有3个变量,具体分类就是:注入方式(2种)、Bean类型(2种)、依赖顺序(2种)。在此,主要分析Spring针对不同生命周期类型的Bean,以不同方式实例化Bean有何不同,在此之后,自然就能明白为何依赖顺序会有关系。

首先对Spring Ioc 容器了解的童鞋一定知道,Ioc的初始化过程与Ioc对Bean依赖关系的注入是分开的(当然特殊情况除外,你懂的,lazy-init),依赖注入的过程是用户第一次向IoC容器索要Bean时触发的。好了,上源码。

再等等,我们先来看个图。下图是我们使用的具体容器实例,即ClassPathXmlApplicationContext的结构图。


1504982230733.jpg

我们知道在Spring IoC的设计中,有个类十分的重要,那就是DefaultListableBeanFactory,因为在设计它时,就已经把IoC重要的功能都纳入了,后面的子类实现大多都是基于它的扩展。ClassPathXmlApplicationContext的父类AbstractRefreshableApplicationContext同样如此:

 //AbstractRefreshableApplicationContext 
/**
     * Create an internal bean factory for this context.
     * Called for each {@link #refresh()} attempt.
     * <p>The default implementation creates a
     * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
     * with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this
     * context's parent as parent bean factory. Can be overridden in subclasses,
     * for example to customize DefaultListableBeanFactory's settings.
     * @return the bean factory for this context
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
     */
  protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

好了,绕了一大圈,回到主题。为什么绕?因为我不绕,突然讲DefaultListableBeanFactory会不会有点突兀哦!下面是他的结构图:


1504982755744.jpg

其实,也还没到他,先来看看他的爷爷--AbstractBeanFactory。到这里,我们就可以正式回归主题了。我们知道,在IoC老祖BeanFactory中定义了getBean(),而依赖注入正是要从该方法说起。AbstractBeanFactory是如何实现该方法的呢?看代码:

   //AbstractBeanFactory中getBean()的实现
    public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException {
        return this.doGetBean(name, requiredType, args, false);
    }

靠,打游击,真正的实现是在doGetBean()方法中:

//妈蛋,这个方法好长。。。单独把有用的代码捞出来吧
//这里就是实际获取Bean的地方,也就是触发依赖注入发生的地方
protected <T> T doGetBean(String name, Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
      **//这里是说先从缓存中去找Bean,若已经有了就不需要重新创建,只针对单例 Bean **
        Object sharedInstance = this.getSingleton(beanName);
          //当前bean正在创建池中,就不要建了,这个是针对原型的Bean
      else {
            if (this.isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
          
           //这里会触发getBean()的递归调用
                String[] dependsOn = mbd.getDependsOn();
          //这里最终会调用ObjectFactory的createBean()
                if (mbd.isSingleton()) {
                  ------> //这里在DefaultSingletonBeanRegistry--》getSingleton<-------
                    sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
                        public Object getObject() throws BeansException {
                            try {
                                return AbstractBeanFactory.this.createBean(beanName, mbd, args);
                            } catch (BeansException var2) {
                                AbstractBeanFactory.this.destroySingleton(beanName);
                                throw var2;
                            }
                        }
                    });
                    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                } else if (mbd.isPrototype()) {//原型bean创建
                  
                        prototypeInstance = this.createBean(beanName, mbd, args);
                
    }

下面来看DefaultSingletonBeanRegistry中getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
               //在创建前,做最终检查,若通过才能重新创建
                this.beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = this.suppressedExceptions == null;
                
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                } catch (IllegalStateException var16) {
                    singletonObject = this.singletonObjects.get(beanName);
                  
                } catch (BeanCreationException var17) {
                
            return singletonObject != NULL_OBJECT ? singletonObject : null;
        }
    }

DefaultSingletonBeanRegistry 中beforeSingletonCreation方法:

    protected void beforeSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }

如果beforeSingletonCreation通过,即满足:1、创建池中没有这个bean;2、改bean不在创建中,那个接下来就是真正的创建bean了。

在AbstractAutowireCapableBeanFactory的doCreateBean方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) {
       
...    1
        if (instanceWrapper == null) {
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }
      ...
     ...   2
    boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
    if (earlySingletonExposure) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
            }

            this.addSingletonFactory(beanName, new ObjectFactory<Object>() {
                public Object getObject() throws BeansException {
                    return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
        Class<?> beanClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
        if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
        } else if (mbd.getFactoryMethodName() != null) {
            return this.instantiateUsingFactoryMethod(beanName, mbd, args);
        } else {
            boolean resolved = false;
            boolean autowireNecessary = false;
            // 无参走这里
            if (args == null) {
                Object var7 = mbd.constructorArgumentLock;
                synchronized(mbd.constructorArgumentLock) {
                    if (mbd.resolvedConstructorOrFactoryMethod != null) {
                        resolved = true;
                        autowireNecessary = mbd.constructorArgumentsResolved;
                    }
                }
            }

            if (resolved) {
                return autowireNecessary ? this.autowireConstructor(beanName, mbd, (Constructor[])null, (Object[])null) : this.instantiateBean(beanName, mbd);
            } else {
                Constructor<?>[] ctors = this.determineConstructorsFromBeanPostProcessors(beanClass, beanName);
                return ctors == null && mbd.getResolvedAutowireMode() != 3 && !mbd.hasConstructorArgumentValues() && ObjectUtils.isEmpty(args) ? this.instantiateBean(beanName, mbd) : this.autowireConstructor(beanName, mbd, ctors, args);
            }
        }
    }

在“1”createBeanInstance中可以看出,调用构造方法创建一个实例对象,如果这个构造方法有参数,而且就是循环依赖的参数,那么这个对象就无法创建了,因为到这里对象没有创建,也没有暴露当前对象,如果是无参的构造方法,那么就可以,先创建一个对象,尽管所有的属性都为空。
在“2”中,申明了必须满足三个条件才能暴露当前创建的对象:
1、该对象是单例;
2、该对象允许循环依赖(默认是true);
3、在当前创建池中有。

至此,我们可以来分析了:
属性注入默认是调用无参构造器创建一个实例,但是属性都为空,并把该对象的引用提前暴露出来,这样依赖于他的Bean就能获取到该Bean,从而能够完成自身的初始化。总结如下:
1、如果循环依赖的都是单例对象(都是通过setter方式注入属性的),那么这个肯定是可以的;
2、如果一个是单例,一个是原型,那么一定要保证单例对象能提前暴露出来,才可以正常注入属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容