内容简介: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框架中服务端模板注入问题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design for Hackers
David Kadavy / Wiley / 2011-10-18 / USD 39.99
Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!