结论
通常来说,如果问Spring内部如何解决循环依赖,一定是默认的单例Bean中,属性互相引用的场景。
原型(Prototype)的场景是不支持循环依赖的,通常会走到
AbstractBeanFactory
类中下面的判断,抛出异常。1
2
3if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}原因很好理解,创建新的A时,发现要注入原型字段B,又创建新的B发现要注入原型字段A…,这就套娃了。
你猜是先StackOverflow还是OutOfMemory?Spring怕你不好猜,就先抛出BeanCurrentlyInCreationException。
基于构造器的循环依赖,就更不用说了,官方文档都摊牌了,你想让构造器注入支持循环依赖,是不存在的,不如把代码改了。
图解
几个Bean之间的互相引用:
自己“循环”依赖自己:
Spring如何解决
在Spring的DefaultSingletonBeanRegistry
类中维护了三个Map,也就是我们通常说的三级缓存:
1 | /** Cache of singleton objects: bean name --> bean instance */ |
- singletonObjects:完成初始化的单例对象的 cache,这里的 bean 经历过 实例化->属性填充->初始化 以及各种后置处理(一级缓存)。
- earlySingletonObjects:存放原始的 bean 对象(完成实例化但是尚未填充属性和初始化),仅仅能作为指针提前曝光,被其他 bean 所引用,用于解决循环依赖的 (二级缓存)。
- singletonFactories:在 bean 实例化完之后,属性填充以及初始化之前,如果允许提前曝光,Spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成 beanFactory 并加入到singletonFactories(三级缓存)。
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
模拟解决实现
先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。
1 | public class CyclicDependence { |
问题解决本质
问题本质是two sum!
two sum是刷题网站leetcode序号为1的题,也就是大多人的算法入门的第一题。
问题内容是:给定一个数组,给定一个数字。返回数组中可以相加得到指定数字的两个索引。
比如:给定nums = [2, 7, 11, 15], target = 9
那么要返回 [0, 1]
,因为2 + 7 = 9
这道题的优解是,一次遍历+HashMap:
1 | public class Solution { |
先去Map中找需要的数字,没有就将当前的数字保存在Map中,如果找到需要的数字,则一起返回。