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、如果一个是单例,一个是原型,那么一定要保证单例对象能提前暴露出来,才可以正常注入属性。

摘自个人简书博客:http://www.jianshu.com/p/95c716ab7851 因为这边篇章而误把另一篇博客覆盖 也不知道能不能恢复

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值