Spring循环依赖正确性及Bean注入的顺序关系详解
一、前言
我们知道Spring可以是懒加载的,就是当真正使用到Bean的时候才实例化Bean。当然也不全是这样,例如配置Bean的lazy-init属性,可以控制Spring的加载时机。现在机器的性能、内存等都比较高,基本上也不使用懒加载,在容器启动时候来加载bean,启动时间稍微长一点儿,这样在实际获取bean供业务使用时,就可以减轻不少负担,这个后面再做分析。我们使用到Bean的时候,最直接的方式就是从Factroy中获取,这个就是加载Bean实例的源头。
最近在做项目时候遇到一个奇葩问题,就是bean依赖注入的正确性与bean直接注入的顺序有关系,但是正常情况下明明是和顺序没关系的啊,究竟啥情况那,不急,让我一一道来。
二、普通Bean循环依赖-与注入顺序无关
2.1循环依赖例子与原理
publicclassBeanA{ privateBeanBbeanB; publicBeanBgetBeanB(){ returnbeanB; } publicvoidsetBeanB(BeanBbeanB){ this.beanB=beanB; } }
publicclassBeanB{ privateBeanAbeanA; publicBeanAgetBeanA(){ returnbeanA; } publicvoidsetBeanA(BeanAbeanA){ this.beanA=beanA; } }
上述循环依赖注入能够正常工作,这是因为Spring提供了EarlyBeanReference功能,首先Spring里面有个名字为singletonObjects的并发map用来存放所有实例化并且初始化好的bean,singletonFactories则用来存放需要解决循环依赖的bean信息(beanName,和一个回调工厂)。当实例化beanA时候会触发getBean(“beanA”);首先看singletonObjects中是否有beanA有则返回:
(1)
ObjectsharedInstance=getSingleton(beanName);//getSingleton(beanName,true); if(sharedInstance!=null&&args==null){ if(logger.isDebugEnabled()){ if(isSingletonCurrentlyInCreation(beanName)){ logger.debug("Returningeagerlycachedinstanceofsingletonbean'"+beanName+ "'thatisnotfullyinitializedyet-aconsequenceofacircularreference"); } else{ logger.debug("Returningcachedinstanceofsingletonbean'"+beanName+"'"); } } //如果是普通bean直接返回,工厂bean则返回sharedInstance.getObject(); bean=getObjectForBeanInstance(sharedInstance,name,beanName,null); }
protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ ObjectsingletonObject=this.singletonObjects.get(beanName); if(singletonObject==null){ synchronized(this.singletonObjects){ singletonObject=this.earlySingletonObjects.get(beanName); if(singletonObject==null&&allowEarlyReference){ ObjectFactorysingletonFactory=(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)
booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&& isSingletonCurrentlyInCreation(beanName));
if(earlySingletonExposure){ if(logger.isDebugEnabled()){ logger.debug("Eagerlycachingbean'"+beanName+ "'toallowforresolvingpotentialcircularreferences"); } addSingletonFactory(beanName,newObjectFactory(){ publicObjectgetObject()throwsBeansException{ returngetEarlyBeanReference(beanName,mbd,bean); } }); }
protectedvoidaddSingletonFactory(StringbeanName,ObjectFactorysingletonFactory){ Assert.notNull(singletonFactory,"Singletonfactorymustnotbenull"); 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允许循环依赖的开关
publicclassTestCircle2{ privatefinalstaticClassPathXmlApplicationContextmoduleContext; privatestaticTesttest; static{ moduleContext=newClassPathXmlApplicationContext(newString[]{"beans-circile.xml"}); moduleContext.setAllowCircularReferences(false); test=(Test)moduleContext.getBean("test"); } publicstaticvoidmain(String[]args){ System.out.println(test.name); } }
ClassPathXmlApplicationContext类中有个属性allowCircularReferences用来控制是否允许循环依赖默认为true,这里设置为false后发现循环依赖还是可以正常运行,翻看源码:
publicClassPathXmlApplicationContext(String[]configLocations)throwsBeansException{ this(configLocations,true,null); }
publicClassPathXmlApplicationContext(String[]configLocations,booleanrefresh,ApplicationContextparent) throwsBeansException{ super(parent); setConfigLocations(configLocations); if(refresh){ refresh(); } }
publicClassPathXmlApplicationContext(String[]configLocations,booleanrefresh,ApplicationContextparent) throwsBeansException{ super(parent); setConfigLocations(configLocations); if(refresh){ refresh(); } }
知道默认构造ClassPathXmlApplicationContext时候会刷新容器。
refresh方法会调用refreshBeanFactory:
protectedfinalvoidrefreshBeanFactory()throwsBeansException{ if(hasBeanFactory()){ destroyBeans(); closeBeanFactory(); } try{ //创建bean工厂 DefaultListableBeanFactorybeanFactory=createBeanFactory(); //定制bean工厂属性 customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized(this.beanFactoryMonitor){ this.beanFactory=beanFactory; } } catch(IOExceptionex){ thrownewApplicationContextException( "I/OerrorparsingXMLdocumentforapplicationcontext["+getDisplayName()+"]",ex); } }
protectedvoidcustomizeBeanFactory(DefaultListableBeanFactorybeanFactory){ 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了,所以测试代码改为:
publicclassTestCircle{ privatefinalstaticClassPathXmlApplicationContextmoduleContext; privatestaticTesttest; static{ //初始化容器上下文,但是不刷新容器 moduleContext=newClassPathXmlApplicationContext(newString[]{"beans-circile.xml"},false); moduleContext.setAllowCircularReferences(false); //刷新容器 moduleContext.refresh(); test=(Test)moduleContext.getBean("test"); } publicstaticvoidmain(String[]args){ System.out.println(test.name); } }
现在测试就会抛出异常:
Causedby:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'beanA'definedinclasspathresource[beans-circile.xml]:Cannotresolvereferencetobean'beanB'whilesettingbeanproperty'beanB';nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'beanB'definedinclasspathresource[beans-circile.xml]:Cannotresolvereferencetobean'beanA'whilesettingbeanproperty'beanA';nestedexceptionisorg.springframework.beans.factory.BeanCurrentlyInCreationException:Errorcreatingbeanwithname'beanA':Requestedbeaniscurrentlyincreation:Isthereanunresolvablecircularreference?
三、工厂Bean与普通Bean循环依赖-与注入顺序有关
3.1测试代码
工厂bean
publicclassMyFactoryBeanimplementsFactoryBean,InitializingBean{ privateStringname; privateTesttest; publicStringgetName(){ returnname; } publicvoidsetName(Stringname){ this.name=name; } publicDependentBeangetDepentBean(){ returndepentBean; } publicvoidsetDepentBean(DependentBeandepentBean){ this.depentBean=depentBean; } privateDependentBeandepentBean; publicObjectgetObject()throwsException{ returntest; } publicClassgetObjectType(){ //TODOAuto-generatedmethodstub returnTest.class; } publicbooleanisSingleton(){ //TODOAuto-generatedmethodstub returntrue; } publicvoidafterPropertiesSet()throwsException{ System.out.println("name:"+this.name); test=newTest(); test.name=depentBean.doSomething()+this.name; } }
为了简化,只写一个public的变量
publicclassTest{ publicStringname; }
publicclassDependentBean{ publicStringdoSomething(){ return"hello:"; } @Autowired privateTesttest; }
xml配置
其中工厂BeanMyFactoryBean作用是对Test类的包装,首先对MyFactoryBean设置属性,然后在MyFactoryBean的afterPropertiesSet方法中创建一个Test实例,并且设置属性,实例化MyFactoryBean最终会调用getObject方法返回创建的Test对象。这里MyFactoryBean依赖了DepentBean,而depentBean本身有依赖了Test,所以这是个循环依赖
测试:
publicclassTestCircle2{ privatefinalstaticClassPathXmlApplicationContextmoduleContext; privatestaticTesttest; static{ moduleContext=newClassPathXmlApplicationContext(newString[]{"beans-circile.xml"}); test=(Test)moduleContext.getBean("test"); } publicstaticvoidmain(String[]args){ System.out.println(test.name); } }
结果:
Causedby:org.springframework.beans.factory.BeanCreationException:Errorcreatingbeanwithname'com.alibaba.test.circle.DependentBean#1c701a27':Autowiringoffieldsfailed;nestedexceptionisorg.springframework.beans.factory.BeanCreationException:Couldnotautowirefield:privatecom.alibaba.test.circle.Testcom.alibaba.test.circle.DependentBean.test;nestedexceptionisorg.springframework.beans.factory.BeanCurrentlyInCreationException:Errorcreatingbeanwithname'test':FactoryBeanwhichiscurrentlyincreationreturnednullfromgetObject
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.
下面列下Springbean创建的流程:
getBean()->创建实例->autowired->set属性->afterPropertiesSet
也就是调用getObject方法早于afterPropertiesSet方法被调用了。
那么我们修改下MyFactoryBean为如下:
publicObjectgetObject()throwsException{ //TODOAuto-generatedmethodstub if(null==test){ afterPropertiesSet(); } returntest; }
publicvoidafterPropertiesSet()throwsException{ if(null==test){ System.out.println("name:"+this.name); test=newTest(); 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时候需要autowiredMyFactoryBean的实例,然后要调用afterPropertiesSet前调用getObject方法所以返回null。
那如果先创建DepentBean,然后在创建MyFactoryBean那?下面分析下过程:
首先会实例化DepentBean,并且加入到singletonFactories
DepentBean实例会autowiredTest,所以会先创建Test实例
创建Test实例,然后加入singletonFactories
Test实例会属性注入DepentBean实例,所以会getBean(“depentBean”);
getBean(“depentBean”)发现singletonFactories中已经有depentBean了,则返回depentBean对象
因为depentBean不是工厂bean所以直接返回depentBean
Test实例会属性注入DepentBean实例成功,Test实例初始化OK
DepentBean实例会autowiredTest实例OK
按照这分析先创建DepentBean,然后在实例化MyFactoryBean是可行的,修改xml为如下:
测试运行结果:
name:zlx
hello:zlx
果真可以了,那按照这分析,上面XML配置如果调整了声明顺序,肯定也是会出错的,因为test创建比dependentBean早,测试下果然如此。另外可想而知工厂bean循环依赖工厂bean时候无论声明顺序如何必然也会失败。
3.3一个思考
上面先注入了MyFactoryBean中需要使用的dependentBean,然后注入MyFactoryBean,问题就解决了。那么如果需要在另外一个Bean中使用创建的id=”test”的对象时候,这个Bean该如何注入那?
类似下面的方式,会成功?留给大家思考^^
publicclassUseTest{ @Autowired privateTesttest; }
四、总结
普通Bean之间相互依赖时候Bean注入顺序是没有关系的,但是工厂Bean与普通Bean相互依赖时候则必须先实例化普通bean,这是因为工厂Bean的特殊性,也就是其有个getObject方法的缘故。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。