在说什么是循环依赖之前,不妨使用代码,先抛出这个异常,bug重现一下。
工程目录如下:
注意: 本文中只需要一个配置文件:spring.xml,两个bean:User和Role。
User.java
代码如下:
public class User {
private Role role;
public User(Role role) {
this.role = role;
}
public void sayUser(){
System.out.println("this is user model");
}
}
Role.java
:
public class Role {
private User user;
public Role(User user) {
this.user = user;
}
public void sayRole(){
System.out.println("this is Role model");
}
}
spring.xml
:
<bean id="user" class="com.lavendor.model.User">
<constructor-arg ref="role"/>
bean>
<bean id="role" class="com.lavendor.model.Role">
<constructor-arg ref="user"/>
bean>
Test.java
:
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
User user = (User) ctx.getBean("user");
user.sayUser();
}
}
运行此main
方法,会抛出如上BeanCurrentlyInCreationException
,BeanCreationException
异常。
至此,bug已经重现,下面分析下为何产生此bug。
简单来说就是,实例化user
时,需要引用role
,但是当前引用的role
正在实例化,要完成实例化role
,必须要user
完成实例化。如此互相牵制,形成一个依赖循环,故而抛出异常。
为什么为产生循环依赖呢?在此之前,有必要先理清一下spring
中bean
的加载过程,参考文章:spring bean的初始化过程
下面是其中几步的实例化步骤:
1.调用bean的构造方法初始化
2.设置bean中属性值(调用setter方法)
在本例中,bean user
注入方式使用的是构造方法注入,而user
的加载方式是先调用构造方法,构造方法初始化又依赖于bean role
的初始化结果,而bean role
也是同样依赖于user
的初始化结果,如此,则构成循环依赖。
上面简单分析了构成循环依赖的原因:因为使用的是构造注入,在初始化的时候即造成循环依赖
。由此可以想到,使用setter
方法注入会不会产生循环依赖呢?
修改代码如下:
1.去掉User.java Role.java
中的构造方法,生成属性的setter方法:
/**
* Role类
*/
public class Role {
private User user;
public void setUser(User user) {
this.user = user;
}
/*public Role(User user) {
this.user = user;
}*/
public void sayRole(){
System.out.println("this is Role model");
}
}
/**
* User类
*/
public class User {
private Role role;
public void setRole(Role role) {
this.role = role;
}
/*public User(Role role) {
this.role = role;
}*/
public void sayUser(){
System.out.println("this is user model");
}
}
2.修改spring.xml
中bean的注入方式为setter注入:
<bean id="user" class="com.lavendor.model.User">
<property name="role" ref="role"/>
bean>
<bean id="role" class="com.lavendor.model.Role">
<property name="user" ref="user"/>
bean>
最后执行Test.java
程序运行正常,问题得以解决。
前文提到过bean
加载顺序中步骤2可知,在初始化bean
时,先调用构造函数,再调用setter方法。spring可以在调用了默认构造函数之后,并且为填充属性值之前的中间,向外暴露一个可调用此正在创建中的bean的单例工厂方法,以便于拿到此bean的引用,那么即可完成循环的实例化。
由此可知,setter注入解决循环依赖原理大致如下:
1.user
初始化,先调用默认的构造函数(无参数)创建一个实例,并未完成其属性值赋值等初始化工作。并且其他bean能够调用它;
2.role
初始化,调用默认的构造函数(无参数)创建一个实例,并未完成其属性值赋值等初始化工作,并且其他的bean能够调用它;
3.user
能够拿到 role
实例的引用,填充自身依赖属性,完成初始化;而后role
也以同样的方式完成自身的初始化。
本文只是探讨了基于xml的循环依赖问题,基于注解的以后探讨。如有不当之处,敬请指正。