一、循环依赖与加载类型、作用域的关系
在开始介绍各种情况之前,首先要了解以下三个知识点:
Spring中依赖注入的方式有两种,属性注入与构造器注入。上面的代码中,类A就是通过属性注入的方式注入了B,类B是通过构造器注入的方式注入了A。
Spring中的bean根据作用域的不同,可以大体分为两类,singleton和prototype。singleton在一个容器中,只会有一个实例;而prototype在每次调用时,都会产生一个新的实例。
Spring中,单例bean有延迟加载和立即加载两种加载方式,其中立即加载模式会在容器启动的时候就创建bean,而延迟加载会在容器启动后,使用到bean的时候再加载它。本篇分析一律使用延迟加载,因为有时候单例bean的加载顺序,会影响到创建bean的成功或失败。
如果循环依赖的bean都是通过构造器注入依赖,那么不管它们是singleton还是prototype,都会失败。
如果循环依赖的bean都是prototype,那么不管它们是通过构造器注入依赖还是通过属性注入依赖,都会失败。
如果循环依赖的bean既有构造器注入也有属性注入,既有singleton也有prototype,在容器启动后,只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功,别的情况都会失败。
所以最终结论就是:
如果多个bean存在循环依赖,在Spring容器启动后,只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功,别的情况都会失败
各种情况测试结果及分析:https://blog.csdn.net/chen2526264/article/details/80673598
二、setting注入避免循环依赖的问题
Spring容器启动后,如果我们去获取singletonA,那么容器的操作步骤大致如下:
1、尝试创建bean singletonA,发现singletonA是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个A的实例,并保存对它的引用,并且将singletonA标记为“正在创建中的singleton”。然后发现singletonA依赖了singletonB,所以尝试创建singletonB。
2、尝试创建bean singletonB,发现singletonB是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个B的实例,并保存对它的引用,并且将singletonB标记为“正在创建中的singleton”。然后发现singletonB依赖了singletonA,所以尝试创建singletonA。
3、尝试创建singletonA,注意,这时Spring容器发现singletonA“正在创建中”,那么就不会再去创建singletonA,而是返回容器之前保存了的对singletonA的引用(首先会读取singletonObjects
缓存中的实例,如果存在则直接返回。因为当我们实例化完成的时候,会通过addSingleton
加入到缓存,所以Address注入User的时候不会重新加载一遍,只是从缓存中直接读取,所以不会有循环引用的问题)。
4、容器将singletonA通过setter方法注入到singletonB,从而singletonB完成创建。
5、容器将singletonB通过setter方法注入到singletonA,从而singletonA完成创建。