论Spring中循环依赖的正确性与Bean注入的顺序关系

1、前言

最近在作项目时候遇到一个奇葩问题,就是bean依赖注入的正确性与bean直接注入的顺序有关系,可是正常状况下明明是和顺序不要紧的啊,究竟啥状况那,不急,让我一一道来。spring

2、普通Bean循环依赖-与注入顺序无关

2.1 循环依赖例子与原理

public class BeanA {

    private BeanB beanB;

    public BeanB getBeanB() {
        return beanB;
    }

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}
public class BeanB {

    private BeanA beanA;

    public BeanA getBeanA() {
        return beanA;
    }

    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}
<bean id="beanA" class="com.alibaba.test.circle.BeanA">
    <property name="beanB">
        <ref bean="beanB" />
    </property>
</bean>
<bean id="beanB" class="com.alibaba.test.circle.BeanB">
    <property name="beanA">
        <ref bean="beanA" />
    </property>
</bean>

上述循环依赖注入可以正常工做,这是由于Spring提供了EarlyBeanReference功能,首先Spring里面有个名字为singletonObjects的并发map用来存放全部实例化而且初始化好的bean,singletonFactories则用来存放须要解决循环依赖的bean信息(beanName,和一个回调工厂)。当实例化beanA时候会触发getBean("beanA");首先看singletonObjects中是否有beanA有则返回:并发

(1)
Object sharedInstance = getSingleton(beanName);//getSingleton(beanName,true);
if (sharedInstance != null && args == null) {
    if (logger.isDebugEnabled()) {
        if (isSingletonCurrentlyInCreation(beanName)) {
            logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                    "' that is not fully initialized yet - a consequence of a circular reference");
        }
        else {
            logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
        }
    }
        // 若是是普通bean直接返回,工厂bean则返回sharedInstance.getObject();
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

一开始确定没有因此会实例化beanA,若是设置了allowCircularReferences=true(默认为true)而且当前bean为单件而且该bean目前在建立中,则初始化属性前把该bean信息放入singletonFactories单件map里面:app

(2)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, new ObjectFactory() {
        public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}

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);
        }
    }
}

而后对该实例进行属性注入beanB,属性注入时候会getBean("beanB"),发现beanB 不在singletonObjects中,就会实例化beanB,而后放入singletonFactories,而后进行属性注入beanA,而后触发getBean("beanA");这时候会到(1)getSingleton返回实例化的beanA。到此beanB初始化完毕添加beanB 到singletonObjects而后返回,而后beanA 初始化完毕,添加beanA到singletonObjects而后返回测试

2.2 容许循环依赖的开关

public class TestCircle2 {

    private final static ClassPathXmlApplicationContext moduleContext;
    private static Test test;
    static {
        moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
        moduleContext.setAllowCircularReferences(false);
        test = (Test) moduleContext.getBean("test");
    }

    
    public static void main(String[] args)  {

        System.out.println(test.name);
    }
}

ClassPathXmlApplicationContext类中有个属性allowCircularReferences用来控制是否容许循环依赖默认为true,这里设置为false后发现循环依赖仍是能够正常运行,翻看源码:this

public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
        this(configLocations, true, null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

知道默认构造ClassPathXmlApplicationContext时候会刷新容器。
refresh方法会调用refreshBeanFactory:spa

protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 建立bean工厂
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        //定制bean工厂属性
        customizeBeanFactory(beanFactory);
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException(
                "I/O error parsing XML document for application context [" + getDisplayName() + "]", ex);
    }
}

protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
    if (this.allowBeanDefinitionOverriding != null) {
        beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding.booleanValue());
    }
    if (this.allowCircularReferences != null) {
        beanFactory.setAllowCircularReferences(this.allowCircularReferences.booleanValue());
    }
}

到这里就知道了,咱们在调用 moduleContext.setAllowCircularReferences(false)前,spring留出的设置bean工厂的回调customizeBeanFactory已经执行过了,最终缘由是,调用设置前,bean工厂已经refresh了,因此测试代码改成:debug

public class TestCircle {

    private final static ClassPathXmlApplicationContext moduleContext;
    private static Test test;
    static {
        //初始化容器上下文,可是不刷新容器
        moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"},false);
        moduleContext.setAllowCircularReferences(false);

        //刷新容器
        moduleContext.refresh();
        test = (Test) moduleContext.getBean("test");
    }

    
    public static void main(String[] args)  {

        System.out.println(test.name);
    }

}

如今测试就会抛出异常:code

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

3、工厂Bean与普通Bean循环依赖-与注入顺序有关

3.1 测试代码

工厂bean
public class MyFactoryBean implements FactoryBean,InitializingBean{

    private String name;
    
    private Test test;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    

    public DependentBean getDepentBean() {
        return depentBean;
    }

    public void setDepentBean(DependentBean depentBean) {
        this.depentBean = depentBean;
    }

    private DependentBean depentBean;
    


    public Object getObject() throws Exception {

        return test;
    }

    public Class getObjectType() {
        // TODO Auto-generated method stub
        return Test.class;
    }

    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return true;
    }

    public void afterPropertiesSet() throws Exception {
            System.out.println("name:" + this.name);
            test = new Test();
            test.name =  depentBean.doSomething() + this.name;

    }
 }
为了简化,只写一个public的变量
public class Test {
    public String name;

}
public class DependentBean {

    public String doSomething(){
        return "hello:";
    }
    
    @Autowired
    private Test test;
}
xml配置
<bean id="test" class="com.alibaba.test.circle.MyFactoryBean">

    <property name="depentBean">
        <bean  class="com.alibaba.test.circle.DependentBean"></bean> 
    </property>

    <property name="name" value="zlx"></property>
</bean>

其中工厂Bean MyFactoryBean做用是对Test类的包装,首先对MyFactoryBean设置属性,而后在MyFactoryBean的afterPropertiesSet方法中建立一个Test实例,而且设置属性,实例化MyFactoryBean最终会调用getObject方法返回建立的Test对象。这里MyFactoryBean依赖了DepentBean,而depentBean自己有依赖了Test,因此这是个循环依赖xml

测试:对象

public class TestCircle2 {

    private final static ClassPathXmlApplicationContext moduleContext;
    private static Test test;
    static {
        moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
        test = (Test) moduleContext.getBean("test");
    }

    
    public static void main(String[] args)  {

        System.out.println(test.name);
    }
}

结果:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.alibaba.test.circle.DependentBean#1c701a27': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.alibaba.test.circle.Test com.alibaba.test.circle.DependentBean.test; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'test': FactoryBean which is currently in creation returned null from getObject

3.2 分析缘由

  • 当实例化test时候会触发getBean("test"),会看当前bean是否存在
  • 不存在则建立Test 的实例,建立完毕后会把当前bean信息放入singletonFactories单件map里面
  • 而后对该实例进行属性注入depentBean,属性注入时候会getBean("depentBean"),
  • 发现depentBean 不存在,就会实例化depentBean,而后放入singletonFactories,
  • 而后进行autowired注入test,而后触发getBean("test");这时候会到(1)getSingleton返回实例化的test。因为test是工厂bean因此返回test.getObject();
  • 而MyFactoryBean的afterPropertiesSet还没被调用,因此test.getObject()返回null.

下面列下Spring bean建立的流程:
getBean()->建立实例->autowired->set属性->afterPropertiesSet

也就是调用getObject方法早于afterPropertiesSet方法被调用了。

那么咱们修改下MyFactoryBean为以下:

public Object getObject() throws Exception {
    // TODO Auto-generated method stub
    if(null == test){
        afterPropertiesSet();
    }
    return test;
}

public void afterPropertiesSet() throws Exception {
    if(null == test){
        System.out.println("name:" + this.name);
        test = new Test();
        test.name =  depentBean.doSomething() + this.name;

    }
}

也就是getObject内部先判断不如test==null那调用下afterPropertiesSet,而后afterPropertiesSet内部若是test==null在建立Test实例,看起来貌似不错,好想能够解决咱们的问题。可是实际上仍是不行的,由于afterPropertiesSet内部使用了depentBean,而此时depentBean=null。

3.3 思考如何解决

3.2分析缘由是先建立了MyFactoryBean,并在在建立MyFactoryBean的过程当中有建立了DepentBean,而建立DepentBean时候须要autowired MyFactoryBean的实例,而后要调用afterPropertiesSet前调用getObject方法因此返回null。

那若是先建立DepentBean,而后在建立MyFactoryBean那?下面分析下过程:

  • 首先会实例化DepentBean,而且加入到singletonFactories
  • DepentBean实例会autowired Test,因此会先建立Test实例
  • 建立Test实例,而后加入singletonFactories
  • Test实例会属性注入DepentBean实例,因此会getBean("depentBean");
  • getBean("depentBean") 发现singletonFactories中已经有depentBean了,则返回depentBean对象
  • 由于depentBean不是工厂bean因此直接返回depentBean
  • Test实例会属性注入DepentBean实例成功,Test实例初始化OK
  • DepentBean实例会autowired Test实例OK

按照这分析先建立DepentBean,而后在实例化MyFactoryBean是可行的,修改xml为以下:

<bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> 

<bean id="test" class="com.alibaba.test.circle.MyFactoryBean">

    <property name="depentBean">
     <ref bean="dependentBean" /> 
    </property>
    
    <property name="name" value="zlx"></property>

</bean>

测试运行结果:
name:zlx
hello:zlx

果然能够了,那按照这分析,上面XML配置若是调整了声明顺序,确定也是会出错的,由于test建立比dependentBean早,测试下果真如此。另外可想而知工厂bean循环依赖工厂bean时候不管声明顺序如何须然也会失败。

3.3 一个思考

上面先注入了MyFactoryBean中须要使用的dependentBean,而后注入MyFactoryBean,问题就解决了。那么若是须要在另一个Bean中使用建立的id="test"的对象时候,这个Bean该如何注入那?
相似下面的方式,会成功?留给你们思考^^

public class UseTest {

    @Autowired
    private Test test;
    
}
<bean id="useTest" class="com.alibaba.test.circle.UseTest"></bean> 

<bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> 

<bean id="test" class="com.alibaba.test.circle.MyFactoryBean">

    <property name="depentBean">
     <ref bean="dependentBean" /> 
    </property>
    
    <property name="name" value="zlx"></property>

</bean>

4、 总结

普通Bean之间相互依赖时候Bean注入顺序是没有关系的,可是工厂Bean与普通Bean相互依赖时候则必须先实例化普通bean,这是由于工厂Bean的特殊性,也就是其有个getObject方法的缘故。

相关文章
相关标签/搜索