spring循环依赖

269 阅读5分钟

单例模式下,spring创建bean实例,会使用缓存保存这些实例,并可以解决循环依赖的问题

  1. spring循环依赖的三种场景

  2. spring创建实例的核心方法

  3. 三种场景下的源码

    1. spring循环依赖的三种场景

  •        singleton模式field属性注入循环依赖

  •        prototype模式field属性注入循环依赖

  • 构造器注入循环依赖

    2. spring创建实例的核心方法

AbstractAutowireCapableBeanFactory类下

createBeanInstance:例化,其实也就是调用对象的构造方法实例化对象 

populateBean:填充属性,这一步主要是对bean的依赖属性进行注入

initializeBean:回到一些形如initMethod、InitializingBean等方法

3\. 三种场景下的源码

singleton模式field属性注入循环依赖(因为这种方式最常见,所以优先说)

测试代码

测试代码简单描述:

共有两个类ServiceA3、ServiceB3, ServiceA3和ServiceB3循环依赖

这里使用spring三级缓存,保存单例对象,也可以处理循环依赖的问题

spring创建实例过程的时序图

由于篇幅优先,无法列出全部代码,读者可以使用测试代码在本地调试,即可了解

开始源码:后边的描述都是根据测试代码进行的

spring创建ServiceA3时

创建ServiceA3实例的调用栈

放入第三级缓存前

放入第三级缓存后

创建ServiceA3实例后,spring发现需要为其注入属性ServiceB3, 但是此时ServiceB3还没有实例,因此,spring开始创建ServiceB3实例(执行流程在上边的时序图中有描述)

创建ServiceB3实例的调用栈

放入第三级缓存前

放入第三级缓存后

到这里,ServiceA3 和ServiceB3的实例已经成功创建,并放入到Spring的第三级缓存中,但是此时,还没有向ServiceA3 和 ServiceB3中注入属性。

===========================分隔线=============================

这里开始有不同了,

在时序图中提到: DependencyDescriptor.resolveCandidate中调用了AbstractFactory.getBean 获取属性的对象实例

但是在doGetBean方法中有一个getSingleton方法

这个方法会从三级缓存中先查看是否有已经存在的实例,如果有则对现有的实例进行处理并返回;如果没有则走另外的处理逻辑;

因为之前ServiceA3和ServiceB3已经创建好了实例并放入的第三级缓存中,所以,当执行到getSingleton时,会从第三级缓存中拿到已经创建好的ServiceA3的工厂类实例(工厂类实例内部包含ServiceA3的实例), 然后拿出ServiceA3的实例放入二级缓存(虽然此时的ServiceA3实例中还没有注入属性ServiceB3),并删除第三级缓存中的ServiceA3的工厂类实例(相当于把ServiceA3的实例转移到二级缓存),并返回ServiceA3的实例。

AutowiredAnnotationBeanPostProcessor在获取resolveDependency方法返回后,利用反射把ServiceA3实例注入到实例ServiceB3

ServiceB3的属性被注入完成后已经是处于完整状态,把ServiceB3转移到第一级缓存

转移前

转移后

如此,ServiceB3的实例创建完成。

递归返回后,ServiceA3实例的属性也同样会注入完成,并把ServiceA3的实例从二级缓存转移到一级缓存中; 

转移前

转移后

如此,singleton模式下的循环依赖初始化流程完成。


分隔线*


prototype模式field属性注入循环依赖

测试代码:

与之前的相同,只是ServiceA3 和ServiceB3 都多了一个注解@Scope(GenericBeanDefinition.SCOPE_PROTOTYPE)

使用@Scope(GenericBeanDefinition.SCOPE_PROTOTYPE)注解的类,表示:在初始化spring容器时,不会实例化,而是在每次使用时都会创建一个新的实例

与单利模式下创建对象的流程大体上相同,不同点在于:

 1.单利模式下,类实例的创建时期是spring容器初始化阶段;原型模式下,类实例的创建是在调用时被创建的; 

2.单利模式下,会使用spring三级缓存管理实例;原型模式下,不会使用spring三级缓存 

3.使用单利模式的类,spring容器中的实例只有一个;使用原型模式的类,不会在spring容器中保留,实例数量根据引用实例的次数而定;

源码不同点,原型模式下的处理逻辑:

使用变量prototypesCurrentlyInCreation 保存正处于创建状态中的实例的标识,这里保存的是(首字母小写的)类名字符串,当发现有循环依赖时,抛出异常;

至此,prototype模式下的循环依赖初始化流程完成。


分隔线*


构造器注入循环依赖

spring无法解决构造器的循环依赖,只能抛出异常

测试代码:

调用堆栈

关键代码

spring处理带有参数的构造器循环依赖过程:

 1)从singletonObjects中获取实例(因为还没有创建实例,这里一定会失败) 

2)检查singletonsCurrentlyInCreation中是否存在类的标识,如果存在则抛出BeanCurrentlyInCreationException(beanName),终止程序 

3)缓存正在创建实例的类的标识到singletonsCurrentlyInCreation

4)创建构造器参数的实例(跳转到1)),创建完成则继续下一步 

5)创建实例完成

1. 产生循环依赖时,不会执行5),会在2)中抛出异常,终止程序 

2. 使用到了spring的三级缓存,但是产生构造器循环依赖时,不会创建实例,所以不会向三级缓存中存入任何数据