我们知道 Spring 可以是懒加载的,就是当真正使用到 Bean 的时候才实例化 Bean。当然也不全是这样,例如配置 Bean 的 lazy-init 属性,可以控制 Spring 的加载时机。现在机器的性能、内存等都比较高,基本上也不使用懒加载,在容器启动时候来加载bean,启动时间稍微长一点儿,这样在实际获取 bean 供业务使用时,就可以减轻不少负担,这个后面再做分析。 我们使用到 Bean 的时候,最直接的方式就是从 Factroy 中获取,这个就是加载 Bean 实例的源头。
最近发现一个问题,一些大的公司(国内知名的boss级别公司就那么几家),在面试的过程中,会问到一个基础题:spring怎么实现循环依赖,或者循环依赖的解决方案。今天主要就这个问题做下简单的探讨:
3个简单的bean:TestA,TestB,TestC其中A包含B,B含C,C含A。然后把三类注入到spring容器中。操作如下:
A类:
public class TestA {
private TestB testB;
public TestB getTestB() {
return testB;
}
public void setTestB(TestB testB) {
this.testB = testB;
}
}
B类:
public class TestB {
private TestC testC;
public TestC getTestC() {
return testC;
}
public void setTestC(TestC testC) {
this.testC = testC;
}
}
C类:
public class TestC {
private TestA testA;
public TestA getTestA() {
return testA;
}
public void setTestA(TestA testA) {
this.testA = testA;
}
}
简单的注入到spring中xml如:
<!-- 循环依赖测试 -->
<bean id="testA" class="com.xin.learn.xhyl.vo.TestA" scope="prototype">
<property name="testB" ref="testB"></property>
</bean>
<bean id="testB" class="com.xin.learn.xhyl.vo.TestB" scope="prototype">
<property name="testC" ref="testC"></property>
</bean>
<bean id="testC" class="com.xin.learn.xhyl.vo.TestC" scope="prototype">
<property name="testA" ref="testA"></property>
</bean>
测试类:
public class TestMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");
System.out.println(context.getBean("testA", TestA.class));
}
}
如果你是web项目,运行项目不会报错,但是当你引用的时候,或者运行测试类后发现报错:
Error creating bean with name 'testA' defined in class path resource [spring/applicationContext.xml]: Cannot resolve reference to bean 'testB' while setting bean property 'testB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testB' defined in class path resource [spring/applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'com.xin.learn.xhyl.vo.TestB' to required type 'com.xin.learn.xhyl.vo.TestC' for property 'testC'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [com.xin.learn.xhyl.vo.TestB] to required type [com.xin.learn.xhyl.vo.TestC] for property 'testC': no matching editors or conversion strategy found
大致意思是:在创建testA的时,设置属性testB的时候不能引用testB。 因为这个时候的testB还没有被创建。
解决:当把 scope的值改为singleton时,或者去掉scope(因spring默认的bean就是单例的),运行就正常了。原因:
Spring提供了EarlyBeanReference功能,首先Spring里面有个名字为singletonObjects的并发map用来存放所有实例化并且初始化好的bean,singletonFactories则用来存放需要解决循环依赖的bean信息(beanName,和一个回调工厂)。当实例化beanA时候会触发getBean(“beanA”);
首先看singletonObjects中是否有beanA有则返回,一开始肯定没有所以会实例化beanA,如果设置了allowCircularReferences=true
(默认为true)并且当前bean为单件并且该bean目前在创建中,则初始化属性前把该bean信息放入singletonFactories单件map里面。然后对该实例进行属性注入beanB,属性注入时候会getBean(“beanB”)
,发现beanB 不在singletonObjects中,就会实例化beanB,然后放入singletonFactories,然后进行属性注入beanA,然后触发getBean(“beanA”);
这时候会到(1)getSingleton返回实例化的beanA。到此beanB初始化完毕添加beanB 到singletonObjects然后返回,然后beanA 初始化完毕,添加beanA到singletonObjects然后返回。