前沿
我认为Spring 的2大基石是IOC和AOP,IOC称为控制反转,能够将bean之前的依赖关系交给Spring来维护,开发者无需关注这些,安心写自己的业务逻辑代码即可。但是由于依赖关系随着时间的积累,变得越来越复杂,在某一个时间点,系统启动的时候,告诉你
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
发生了Spring无法解决的循环依赖问题。
这篇文章我们的讨论分为3个方面:循环依赖的情景、Spring能够解决哪些循环依赖、Spring是如何解决循环依赖。
正文
我们新创建2个类User和School
循环依赖场景及Spring能够解决哪些循环依赖
序列 | 依赖方式 | Spring是否能解决 |
1 | 都是通过set方式相互依赖 | 可以 |
2 | 都是通过构造器相互依赖 | 不能 |
3 | User通过构造器与School依赖,School通过set方式与User依赖 | 可以 |
4 | School通过构造器与User依赖,User通过set方式与User依赖 | 可以 |
下面是例子
第一种情况:
@Component
public class User {
private String name;
private School school;
/*User(School school){
this.school = school;
}*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
@Component
public class School {
private User user;
private String name;
/*School(User user) {
this.user = user;
}*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
项目启动后,正常
第二种情况:
@Component
public class User {
private String name;
private School school;
User(School school){
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Component
public class School {
private User user;
private String name;
School(User user) {
this.user = user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
启动后报错,报错信息如下:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| school defined in file [/Users/zanpengfei/workspace/user/target/classes/com/zanpengfei/user/user/circle/School.class]
↑ ↓
| user defined in file [/Users/zanpengfei/workspace/user/target/classes/com/zanpengfei/user/user/circle/User.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
School通过构造器与User依赖,User通过set方式与School依赖
@Component
public class User {
private String name;
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
@Component
public class School {
private User user;
private String name;
School(User user) {
this.user = user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
启动后正常
User通过构造器与School依赖,School通过set方式与User依赖
@Component
public class School {
private User user;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
@Component
public class User {
private String name;
private School school;
User(School school){
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
启动后正常
Spring如何解决循环依赖
Spring通过三级缓存来解决循环依赖,其中一级缓存为单例池(singletonobjects集合),二级缓存为早期曝光对象集合earlysingletonobjects,三级缓存为早期曝光对象工厂(
ingletonFactories集合)
1、当User、School两个类发生循环引用时,在User类完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中。如果User被AOP代理,那么通过这个工厂获取到的就是User代理后的对象,如果User没有被AOP代理,那么这个工厂获取到的就是User实例化的对象。
2、当User进行属性注入时,会去创建School、同时 School又依赖了User、所以创建School的同时又会去调用getBean(user)来获取需要的依赖。此时的getBean(user)会从缓存中获取:
首先先获取到三级缓存中的工厂
而后再调用对象工厂的getobject方法来获取到对应的对象,得到这个对象后将其注入到School中。紧接着School会走完它的生命周期流程(初始化、后置处理器等)。
当School创建完后,会将School再注入到User中,此时User再完成它的整个生命周期。到此循环依赖结束!
Spring为啥不能解决都是通过构造器相互依赖呢?
第一步,User类完成实力化的时候,也就是调用自己的构造器,发送构造器中存在Scholl类,然后开始实例化School类,当调用School构造器的时候发现依赖与User类,再实例化User类,发现User类依赖于School类等等,发生了循环依赖,所以这种情况下不能解决
Spring为啥能解决都是通过set方式相互依赖呢?
第一步,User类进行实例化,调用构造方法,因为无有参构造器,调用其无参构造器,完成实例化,实例化后通过User创建一个工厂,并且添加到三级缓冲中。
第二步,User类进行属性注入,在属性注入的时,会创建School类,School类进行实例化,同样School类无有参构造方法,调用无参构造器进行实例化,实例化后创建一个School工厂,并将其添加到三级缓冲,然后进行属性注入,属性注入时,发现依赖与User,通过调用getBean(user)获取依赖性对象,虽然User类还不完整,但是其已经完成实例化并且将其工厂类添加到了三级缓冲中,getBean(user)会调用User的工厂类返回user对象,将User注入到School上,然后School完成整个生命周期,最后返回School对象给User类,School类注入到了User上,User类继续完成整个生命周期,至此循环依赖结束。
剩下2种情况也是类似的,看完后大家了解Spring解决循环依赖的原理了吗