单例模式下,spring创建bean实例,会使用缓存保存这些实例,并可以解决循环依赖的问题
-
spring循环依赖的三种场景
-
spring创建实例的核心方法
-
三种场景下的源码
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的三级缓存,但是产生构造器循环依赖时,不会创建实例,所以不会向三级缓存中存入任何数据