内容简介:Spring中循环依赖的正确性与Bean注入的顺序关系
一、前言
最近在做项目时候遇到一个奇葩问题,就是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方法的缘故。
如果你也想在IT行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,7个月后,进入名企拿高薪。我们的课程内容有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,群号为: 575745314
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- WiredTiger的时间戳事务设计及其正确性证明
- 流处理系统正确性基石:ExactlyOnce 的设计和实现
- 流处理系统正确性基石:ExactlyOnce的设计和实现
- Mybatis sql 如何进行全自动补全 检测sql的正确性 提升开发效率
- Angular 4 依赖注入教程之二 组件中注入服务
- 服务端注入之Flask框架中服务端模板注入问题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
用户思维+:好产品让用户为自己尖叫
[美] Kathy Sierra / 石航 / 人民邮电出版社 / 2017-9 / 69.00元
畅销产品与普通产品的本质区别是什么?若没有巨额预算、不爱营销噱头、不开奢华的产品发布会,如何打造可持续成功的产品?本书针对上述问题提出了新颖的观点:用户并不关心产品本身有多棒,而是关心使用产品时自己有多棒。作者利用其多年的交互设计经验,生动阐释了这一观点背后的科学。可贵的是,本书并不止步于解释“为什么”,还清晰呈现了“怎么做”。 本书风格活泼、图文并茂,其对话式内容既引人入胜,又引人深思,适......一起来看看 《用户思维+:好产品让用户为自己尖叫》 这本书的介绍吧!