美文网首页Spring程序员
论Spring中循环依赖的正确性与Bean注入的顺序关系

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

作者: 阿里加多 | 来源:发表于2017-07-29 14:56 被阅读203次

    一、前言

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

    二、普通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里面:

    (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后发现循环依赖还是可以正常运行,翻看源码:

    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:

    
    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了,所以测试代码改为:

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

    现在测试就会抛出异常:

    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?
    

    三、工厂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,所以这是个循环依赖

    测试:

    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>
    

    四、 总结

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

    相关文章

      网友评论

        本文标题:论Spring中循环依赖的正确性与Bean注入的顺序关系

        本文链接:https://www.haomeiwen.com/subject/omqclxtx.html