1,什么是 Spring 的循环依赖
简单来讲,就是有一个 A 对象,创建 A 的时候发现 A 对象依赖 B,然后去创建 B 对象的时候,又发现 B 对象依赖 C,然后去创建 C 对象的时候,又发现 C 对象依赖 A。这就是所谓的循环依赖。
那么 Spring 在创建 Bean 的时候是如何解决这种关系的依赖呢?(循环依赖)先抛出结论,Spring 使用了三级缓存解决了循环依赖的问题。并且 Spring 能解决哪些循环依赖不能解决哪些循环依赖。以下会详细的阐述。
2,什么是三级缓存
1,第一级缓存:单例缓存池 singletonObjects。
2,第二级缓存:早期提前暴露的对象缓存 earlySingletonObjects。
3,第三级缓存:singletonFactories 单例对象工厂缓存
3,什么是早期暴露的对象
所谓的早提提前暴露的对象就是说,你是一个不完整的对象,你的属性还没有值,你的对象也没有被初始化。这就是早期暴露的对象,只是提前拿出来给你认识认识。但他非常重要。这是多级缓存解决循环依赖问题的一个巧妙的地方。
4,创建 Bean 的整个过程
getSingleton 方法详解
1,getBean 方法肯定不陌生,必经之路,然后调用 doGetBean,进来以后首先会执行 transformedBeanName 找别名,看你的 Bean 上面是否起了别名。然后进行很重要的一步,getSingleton,这段代码就是从你的单例缓存池中获取 Bean 的实例。那么你第一次进来肯定是没有的,缓存里肯定是拿不到的。也就是一级缓存里是没有的。那么它怎么办呢?他会尝试去二级缓存中去拿,但是去二级缓存中拿并不是无条件的,首先要判断 isSingletonCurrentlyInCreation(beanName) 他要看你这个对象是否正在创建当中,如果不是直接就退出该方法,如果是的话,他就会去二级缓存 earlySingletonObjects 里面取,如果没拿到,它还接着判断 allowEarlyReference 这个东西是否为 true。它的意思是说,是否允许让你从单例工厂对象缓存中去拿对象。默认为 true。好了,此时如果进来那么就会通过 singletonFactory.getObject();去单例工厂缓存中去拿。然后将缓存级别提升至二级缓存也就早期暴露的缓存。然后,如果说你第一次都没有从单例缓存中没有获取到对象并且 isSingletonCurrentlyInCreation 也没有被标记。那么直接就返回了后面的流程都不走。
dependsOn
2,getSingleton 执行完以后会走 dependsOn 方法,判断是否有 dependsOn 标记的循环引用,有的话直接卡死,抛出异常。比如说 A 依赖于 B,B 依赖于 A 通过 dependsOn 注解去指定。此时执行到这里就会抛出异常。这里所指并非是构造函数的循环依赖.
beforeSingletonCreation
3,在这里方法里。就把你的对象标记为了早期暴露的对象。提前暴露对象用于创建 Bean 的实例
createBean
4,紧接着就走创建 Bean 的流程开始。在创建 Bean 之前执行了一下 resolveBeforeInstantiation 这个东东。它的意思是说,代理 AOPBean 定义注册信息(也就是那个蛋)但是这里并不是实际去代理你的对象,因为你只是个蛋。你还没有被创建,我给你代理个锤子啊?只是代理了你的 Bean 定义信息,还没有被实例化。把你的 Bean 定义信息放进缓存,以便我想代理真正的目标对象的时候,直接去缓存里去拿。
doCreateBean
5,接下来就真正的走你的创建 Bean 流程
首先走进真正做事儿的方法 doCreateBean 然后找到 createBeanInstance 这个方法,在这里面它将为你创建你的 Bean 实例信息(Bean 的实例)。如果说创建成功了,那么就把你的对象放入缓存中去(将创建好的提前曝光的对象放入 singletonFactories 三级缓存中)将对象从二级缓存中移除因为它已经不是提前暴露的对象了。但是。如果说在 createBeanInstance 这个方法中在创建 Bean 的时候它会去检测你的依赖关系,会去检测你的构造器。然后,如果说它在创建 A 对象的时候,发现了构造器里依赖了 B,然后它又会重新走 getBean 的这个流程,当在走到这里的时候,又发现依赖了 A 此时就会抛出异常。为什么会抛出异常,因为,走 getBean 的时候他会去从你的单例缓存池中去拿,因为你这里的 Bean 还没有被创建好。自然不会被放进缓存中,所以它是在缓存中拿不到 B 对象的。反过来也是拿不到 A 对象的。造成了死循环故此直接抛异常。这就是为什么 Spring IOC 不能解决构造器循环依赖的原因。因为你还没来的急放入缓存你的对象是不存在的。所以不能创建。同理 @Bean 标注的循环依赖方法也是不能解决的,跟这个同理。那么多例就更不能解决了。为什么?因为在走 createBeanInstance 的时候,会判断是否是单例的 Bean 定义信息 mbd.isSingleton();如果是才会进来。所以多例的 Bean 压根就不会走进来,而是走了另一段逻辑,这里不做介绍。至此,构造器循环依赖和 @Bean 的循环依赖还有多例 Bean 的循环依赖为什么不能解决已经解释清楚。然后如果说,Bean 创建成功了。那么会走后面的逻辑
6,将创建好的 Bean 放入缓存
addSingletonFactory。方法就是将你创建好的 Bean 放入三级缓存中。并且移除早期暴露的对象。
populateBean
7,通过 populateBean 给属性赋值
我们知道,创建好的对象,并不是一个完整的对象,里面的属性还没有被赋值。所以这个方法就是为创建好的 Bean 为它的属性赋值。并且调用了我们实现的的 XXXAware 接口进行回调初始化,。然后调用我们实现的 Bean 的后置处理器,给我们最后一次机会去修改 Bean 的属性。其他的地方我不做过多的介绍,我只讲一个重点就是,在 populateBean 里面他会解析你的属性,并且赋值,当发现,A 对象里面依赖了 B,此时又会走 getBean 方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对 createBeanInstance 对象创建完成以后已经放入了缓存当中,所以创建 B 的时候发现依赖 A,直接就从缓存中去拿,此时 B 创建完,A 也创建完,一共执行了 4 次。至此 Bean 的创建完成,最后将创建好的 Bean 放入单例缓存池中 addSingleton();至此 Ioc 创建 Bean 的整个生命周期已经介绍完毕。以及 Spring 是如何通过三级缓存去解决循环依赖的问题。下面附加一张流程脑图。