一、前言
我们知道 Spring 可以是懒加载的,就是当真正使用到 Bean 的时候才实例化 Bean。当然也不全是这样,例如配置 Bean 的 lazy-init 属性,可以控制 Spring 的加载时机。现在机器的性能、内存等都比较高,基本上也不使用懒加载,在容器启动时候来加载bean,启动时间稍微长一点儿,这样在实际获取 bean 供业务使用时,就可以减轻不少负担,这个后面再做分析。 我们使用到 Bean 的时候,最直接的方式就是从 Factroy 中获取,这个就是加载 Bean 实例的源头。
最近在做项目时候遇到一个奇葩问题,就是bean依赖注入的正确性与bean直接注入的顺序有关系,但是正常情况下明明是和顺序没关系的啊,究竟啥情况那,不急,让我一一道来。
二、普通Bean循环依赖-与注入顺序无关
2.1 循环依赖例子与原理
1
2
3
4
5
6
7
8
9
|
public class BeanA {
private BeanB beanB;
public BeanB getBeanB() {
return beanB;
}
public void setBeanB(BeanB beanB) {
this .beanB = beanB;
}
}
|
1
2
3
4
5
6
7
8
9
|
public class BeanB {
private BeanA beanA;
public BeanA getBeanA() {
return beanA;
}
public void setBeanA(BeanA beanA) {
this .beanA = beanA;
}
}
|
1
2
3
4
5
|
<bean id= "beanA" class = "com.alibaba.test.circle.BeanA" >
<property name= "beanB" >
<ref bean= "beanB" />
</property>
</bean>
|
1
2
3
4
5
|
<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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 );
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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)
1
2
|
boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
|
1
2
3
4
5
6
7
8
9
10
11
|
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);
}
});
}
|
1
2
3
4
5
6
7
8
9
10
|
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 允许循环依赖的开关
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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后发现循环依赖还是可以正常运行,翻看源码:
1
2
3
|
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
this (configLocations, true , null );
}
|
1
2
3
4
5
6
7
8
|
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super (parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
|
1
2
3
4
5
6
7
8
|
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super (parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
|
知道默认构造ClassPathXmlApplicationContext时候会刷新容器。
refresh方法会调用refreshBeanFactory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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);
}
}
|
1
2
3
4
5
6
7
8
|
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了,所以测试代码改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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的变量
1
2
3
|
public class Test {
public String name;
}
|
1
2
3
4
5
6
7
|
public class DependentBean {
public String doSomething(){
return "hello:" ;
}
@Autowired
private Test test;
}
|
xml配置
1
2
3
4
5
6
|
<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,所以这是个循环依赖
测试:
1
2
3
4
5
6
7
8
9
10
11
|
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为如下:
1
2
3
4
5
6
7
|
public Object getObject() throws Exception {
// TODO Auto-generated method stub
if ( null == test){
afterPropertiesSet();
}
return test;
}
|
1
2
3
4
5
6
7
|
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为如下:
1
2
3
4
5
6
7
|
< 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该如何注入那?
类似下面的方式,会成功?留给大家思考^^
1
2
3
4
|
public class UseTest {
@Autowired
private Test test;
}
|
1
2
3
4
5
6
7
8
|
<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方法的缘故。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://segmentfault.com/a/1190000012738048