类A依赖类B,类B也依赖类A,这种情况就会出现循环依赖。 Bean A → Bean B → Bean A
循环依赖会导致内存溢出
public class AService {
private BService bService = new BService();
}
public class BService {
private AService aService = new AService();
}
当你通过 new AService() 创建一个对象时你会获得一个栈溢出的错误。 如果你了解 Java的初始化顺序就应该知道为什么会出现这样的问题。
因为调用 new AService() 时会先去执行属性 bService 的初始化, 而 bService 的初始化又会去执行AService 的初始化, 这样就形成了一个循环调用,最终导致调用栈内存溢出。
StudentA 依赖StudentB,同时 StudentB也依赖StudentA
@Component
public class StudentA {
private String nameA;
@Autowired
private StudentB studentB;
public StudentA( StudentB studentB) {
this.studentB = studentB;
}
}
@Component
public class StudentB {
private String nameB;
@Autowired
private StudentA studentA;
public StudentB( StudentA studengA) {
this.studentA = studentA;
}
}
启动工程,我们会看到如下报错,这就是循环依赖导致的程序运行问题
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-11-07 13:47:13.714 ERROR 12744 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| studentA defined in file [D:\prometheus\student-server\target\classes\com\student\studentserver\entity\xunhaunyilai\StudentA.class]
↑ ↓
| studentB defined in file [D:\prometheus\student-server\target\classes\com\student\studentserver\entity\xunhaunyilai\StudentB.class]
└─────┘
当出现这种循环依赖时,很可能是在设计方面存在问题,没有把每个类的职责很好的区分开。应该尝试正确重新设计组件,以便其层次结构设计良好,并且不需要循环依赖项。如果无法重新设计,那么可以考虑其他解决办法。
解决Spring循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。
@Component
public class StudentA {
private String nameA;
@Autowired
private StudentB studentB;
public StudentA(@Lazy StudentB studentB) {
this.studentB = studentB;
}
}
@Component
public class StudentA {
private String nameA;
@Autowired
private StudentB studentB;
public StudentA(@Lazy StudentB studengB) {
this.studentB = studengB;
}
}
加上@Lazy以后,启动工程后就不报错了
@Component
public class StudentA {
@Autowired
private StudentB studentB;
public void testA(){
System.out.println("student1");
}
}
@Component
public class StudentB {
@Autowired
private StudentA studengA;
public void testB(){
System.out.println("studentB");
}
}
这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了(内部通过三级缓存进行解决)。
三级缓存其实它更像是Spring容器工厂的内的术语,采用三级缓存模式来解决循环依赖问题,这三级缓存分别指
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
// 从上至下 分表代表这“三级缓存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
...
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的 都会放进这里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)
加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决getSingleton()从缓存里获取单例对象步骤分析可知,Spring解决循环依赖的诀窍:就在于singletonFactories这个三级缓存。这个Cache里面都是ObjectFactory,它是解决问题的关键。
流程梳理:
流程图总结:
参考链接:
spring循环依赖与三级缓存
循环依赖及解决方法
整个从创建bean到解决循环依赖的过程:
context.getBean(A.class)->实例化->放入缓存->依赖注入B->getBean(B)->实例化B并放入缓存->B依赖注入A->getBean(A)获取到了缓存中的值并正常返回->B初始化成功->A初始化成功