- 定义:循环依赖是指两个或者多个类之间相互依赖,形成了一个闭环,例如:假设有两个类对象,A类和B类:A类依赖于B类,B类依赖于A类
- 影响:循环依赖导致Spring容器中在初始化类时无法确定哪一个类应先创建
- 分类:根据注入方式的不同,可以分为构造器循环依赖和set属性循环依赖,使用@Autowired注解
构造器循环依赖:是指两个或者多个类通过构造器参数相互依赖:
public class BeanA {
private final BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private final BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
Set循环依赖:是指两个或者多个类通过属性相互依赖:
public class BeanA {
private BeanB beanB;
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
使用@Autowired注解
public class BeanA {
@Autowire
private BeanB beanB;
}
public class BeanB {
@Autowire
private BeanA beanA;
}
- 解决方式:
1.Spring提供三级缓存存储完整的类对象和半成品的类对象的方式来解决循环依赖,三级缓存机制包括3个部分:
内容: 第一部分:单例池(最终存储完整的单例类对象的容器)
单例池是一个Map,用来存储完整的单例类对象的容器,当Spring容器在创建类时,首先会去单例池中去查找是否存在这个类,如果存在则返回,如果不存在则创建这个类
第二部分:早期类对象池:
早期类对象池是一个Map,用来存放部分初始化完成的单例类,且当前类已经被其他类引用,当Spring检测到循环依赖时,会将部分初始化完成的类对象存放到该池中,以便其他类对象能够引用
第三部分:单例类的工厂池
单例类的工厂池是用来缓存半成品的类,且未被引用.是一个Map,用来存放类工厂,类工厂是一个用来创建类事例的对象,当需要创建类实例时,通过类工厂进行创建
流程:
1.UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
2.UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
3.UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
4.UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
5.UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
6.UserService 注入UserDao;
7.UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
2.使用@Lazy注解:可以使用@Lazy注解的方式来延迟类的初始化,从而避免循环依赖:
public class BeanA {
@Autowired
@Lazy
private BeanB beanB;
}
public class BeanB {
@Autowired
private BeanA beanA;
}
3.使用代理对象;使用代理对象也是解决循环依赖的方法之一,Spring AOP通过动态代理机制创建类的代理对象,在一定程度上可以解决循环依赖
5.注意事项:
1.避免构造器循环依赖
原因:构造器循环依赖无法通过三级缓存的方式解决循环依赖,因为构造器循环依赖会导致Spring无法实例化任何一个类
- 存在的问题:
- 代码难以维护:循环依赖会使代码逻辑复杂,增加代码的维护难度
- 性能问题:频繁使用三级缓存会影响性能
- 潜在的内存泄露:不正确的依赖管理会导致内存泄漏,影响程序的稳定性