# 首先我们先简单看一下Bean对象的创建流程
什么是bean对象? 其实就是我们平常写的类。当你把在类上面加了例如@component、@configuration注解以后。当容器启动以后。我们就不用在去通过new对象的方式来调用。直接通过属性注入的方式即可。
# 什么情况会出现循环依赖
@Controller
public class A {
// A注入B
@Autowired
private B b;
}
@Controller
public class B {
// B注入A
@Autowired
private A a;
}
当容器启动以后会去创建A对象。首先进行实例化->进行属性B对象注入->发现容器当中没有B对象。就会去创建B对象->实例化B对象->进行A对象属性注入->发现Spring容器中也没有A对象->创建A对象。这个时候就会出现循环依赖的问题。
# Spring是如何解决循环依赖的
傻子都知道--三级缓存
/** Cache of singleton objects: bean name to bean instance. */
// 一级缓存,存放的是实例化和初始化都完成的完整对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
// 二级缓存,存放的是实例化完成但初始化未完成的对象
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
// 三级缓存,存放的是实例化完成但初始化未完成的对象,特别注意的是,这里存放value是一个匿名内部类
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
首先我们知道循环依赖只会出现在属性注入的时候接下来我们就详细讲一下 属性注入的全过程因为这里源码看起来太费劲了。我们就展示少量源码直接展示真正的原理
假设我们这里容器启动以后还是去创建上面的A,B两个对象---这里我们讲的都是单例的情况。只有单例才会出现循环依赖。
实例化A普通对象-->放入三级缓存singletonFactories.put('A',getEarlyBeanReference(beanName, mbd, bean))并同时放入 singletonsCurrentlyInCreation中 这是一个set集合(这个集合的作用就是代表当前这个对象正在创建中。对象创建完了以后就会从集合中删除)
填充B对象->先去单例池Map中去找有没有B对象->没有->创建B对象
2.1 实例化B普通对象->放入三级缓存
2.2 填充A对象->先去单例池singletonObjects一级缓存中找-没有—>去singletonsCurrentlyInCreation中找一下如果有就代表A对象创建中也代表会出现循环依赖->去二级缓存earlySingletonObjects找-没有->去三级缓存singletonFactories-去找(正常都会找到)->获得对象以后进行AOP代理-生成A的代理对象->放入二级缓存(并且去删除三级缓存中的对象)->去执行B对象的初始化阶段-返回完整的B代理对象->放入一级缓存池当中(删除二级缓存中的代理对象)。B对象填充成功以后进入A对象的初始化阶段
这块有个点要注意的是这个时候如果执行到了A对象的AOP阶段的时候我们会先去缓存中找有没有A的代理对象如果有的话就直接获取到执行一些其他代理逻辑。如果没有的话就对A对象重新进行AOP
对象创建成功
remove掉set集合中的A对象
# 简单总结一下:
- 首先Spring会如何判断是否会发生循环依赖的:从上面描述中我们可以看到Spring获取对象时去单例池先去单例池singletonObjects一级缓存中找发现没有以后回去singletonsCurrentlyInCreation中这个集合中。如果有说明什么?说明当前这个对象正在创建当中。因此可能会发生循环依赖。
- 为什么Spring需要三级缓存才能解决循环依赖。如果只有俩级缓存呢:其实就是为了保持单例。通过上述描述我们可以知道Spring是从第三级缓存中找到对应的对象以后进行AOP的。如果没有第三级缓存。就会出现多次创建AOP代理。生成的对象也不一样。也就没办法保持单例了。
- Spring是如何打破循环依赖的: 是通过第三级缓存打破的。开头我们说到。实例化A对象以后。会将A实例化出的普通对象放入三级缓存中。其实这块我们放入的并不是一个真正的对象。通过源码我们可以知道。其实放入的是一个lambda表达式。当B对象通过第三级缓存通过Bean名字获取到这个lambda时。下一步直接就是执行AOP过程。然后放入二级缓存中。