「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」
前言
这里提前了解一些AOP的相关内容。
AOP是在初始化后做的:(applyBPAfterInitialization)
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
这个功能需要在启动配置类上加上@EnableAspectJAutoProxy,但SpringBoot中默认就启用了。
加上注解之后,就会把AnnotationAwareAspectJAutoProxyCreator这个类的PP代理,提前注册到beanFactory中。
循环依赖问题,以及如何处理
什么是循环依赖?
- 简单来说,就是A中有B,B中有A。
- 简单的循环依赖并不是一个困难的问题,Spring中的循环依赖则并不是一个简单的工作。
Spring中的循环依赖是如何产生的?
-
A中autowired了一个B,B中也autowired了一个A。
-
假设先是A来执行生命周期:
- 实例化A,得到一个对象
- 填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就创建B【1】
- 填充其他属性,初始化前BP,初始化,初始化后,放入单例池
-
那么B的生命周期:
- 实例化B,得到一个对象
- 填充B中的A属性(根据A属性类型,名称去查找),去单例池中获取A(getSingleton()),获取不到就创建A【2】
- 填充其他属性,初始化前BP,初始化,初始化后,放入单例池
-
不难发现,由于放入单例池是在填充属性完毕之后才进行的,因此流程1和流程2,如果没有别的特殊处理,那么就会陷入死循环了,因此这篇文章就是来探究Spring中是如何解决这个问题的。
这里的解决方法就是三级缓存:
singletonObjects(单例池)
earlySingletonObjects(关键的解决方式)
singletonFactories
以及以下两个容器:
singletonsCurrentlyInCreation
earlyProxyReferences
打破循环的关键,就是新增一个缓存,在步骤1/2之前,先把实例化但并没有填充属性的对象,放到这个缓存里。(我们这里称为缓存A)
那么上述的流程,就变成了如下过程,注意加粗的不同部分:
假设先是A来执行生命周期:
- 实例化A,得到一个对象,并把这个还没填充属性的A放到缓存A中。
- 填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取缓存A中的,还获取不到就去创建B。
- 填充其他属性,初始化前BP,初始化,初始化后,放入单例池
接下来进入到B的生命周期:
- 实例化B,得到一个对象,并把这个还没填充属性的A放到缓存A中。
- 填充B中的A属性(根据A属性类型,名称去查找),去单例池中获取A(getSingleton()),获取不到就取缓存A中的,此时能获取到了,就赋值。【3】
- 填充其他属性,初始化前BP,初始化,初始化后,放入单例池。
根据这个思路,这样子就解决了上面的循环依赖的问题了。
这里【3】中,A此时的数据是未完全赋值的,因此从【3】中取的对象A,和beanA必须是同一个对象,这样才能让【3】中写入的A,最后属性是完整的。【3:1】
根据这个思路,除了原来已有的单例池,只要多增加一个缓存即可,但是Spring中却有三级缓存多了一个,这个缓存是做什么的呢?
- 多出来的这级缓存,是和代理相关的。
我们接着看A的生命周期:
- 实例化A,得到一个对象,并把这个还没填充属性的A放到缓存A中。
- 填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取缓存A中的,还获取不到就去创建B。
- 填充其他属性
- 初始化前BP
- 初始化
- 初始化后 - > 在这里,我们会进行AOP的处理,如果有就会生成代理对象。
- 放入单例池 - > 在这里,如果上面AOP处理了,这里就是代理对象了。【4】
看,这里【3:1】和【4】就冲突了:【4】放入单例池的对象,和赋给B的对象就不同了。
如何解决这个问题?有一个思路是这样的:
- 实例化A,得到一个对象,在这里直接进行AOP的代理,并把这个还没填充属性的A放到缓存A中。
- 填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取缓存A中的,还获取不到就去创建B。
- 填充其他属性
- 初始化前BP
- 初始化
- 初始化后 - >这里不用再来了。
- 放入单例池 - > 在这里,如果上面AOP处理了,这里就是代理对象了。【4】
如果这种思路,其实也是不需要多一级缓存的。但这里不另加一层缓存,和设计上有冲突:
- AOP实际上是填充属性后才进行的,需要在填充属性之前就做AOP?
到这里我们知道了:
-
如果要解决循环依赖的问题,那么也必须要提前进行AOP,否则会出现注入对象不一致的情况。
-
因此,Spring采取了这种设计:A出现循环依赖的情况下,进行提前AOP(实例化之后就进行AOP)
那么,如何在实例化后,就能判断A出现了循环依赖?
- 如果需要判断循环依赖,在B的生命周期【3】中,是比较合适的判断点,而一开始我们死循环的进入点,也是这里。 并且,我们在生命周期一开始的时候,就把当前正在创建的bean的名称,存到singletonsCurrentlyInCreation这个容器里。现在流程就变成了:
A的:
-
放beanNameA到singletonsCurrentlyInCreation这个容器里。
-
实例化A,得到一个对象,并把这个还没填充属性的A放到缓存A中。
-
填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取缓存A中的,还获取不到就去创建B。
接下来进入到B的生命周期:
-
放beanNameB到singletonsCurrentlyInCreation这个容器里。
-
实例化B,得到一个对象,并把这个还没填充属性的B放到缓存A中。
-
填充B中的A属性(根据A属性类型,名称去查找),
- 这里就能发现循环依赖了:singletonsCurrentlyInCreation里有A!这里A就出现了循环依赖,而且比较好判断;根据上面的分析,这里我们就会进行对A的判断:
- 如果A需要进行AOP,则进行AOP获取代理对象;
- 如果不需要AOP:去单例池中获取A(getSingleton()),获取不到就取缓存A中的,此时能获取到了,就赋值。
- 这里就能发现循环依赖了:singletonsCurrentlyInCreation里有A!这里A就出现了循环依赖,而且比较好判断;根据上面的分析,这里我们就会进行对A的判断:
-
填充其他属性,初始化前BP,初始化,初始化后,放入单例池。
回到A生命周期
- 填充其他属性---【5】,初始化前BP,初始化,初始化后,放入单例池。
不妨把问题考虑得更复杂一些,【5】之中:
此时A中有个C,C中也有个A,此时A需要填充C属性,就进到C的bean生命周期:
-
.......
-
.......
-
填充C中的A属性:(与上面相同)
-
此时我们就能了解到为什么需要多一个缓存了:
-
如果我们没有地方来记录生成的A的AOP代理对象,那么这里就违反了单例的思想了:
B和C中的A的代理对象不是同一个!
因此,B生成的A的AOP对象,就需要存到一个用来存储生成的代理对象的池里,这样在后面同样发生依赖注入的点上,才能使得这些AOP的代理对象都是同一个:这个池子就是earlySingletonObjects。
但是,AOP需要的是代理,代理中需要原始对象作为target,这也就意味着这里其实也需要原始对象A。
现在问题就变成了:我们需要从缓存A中,尝试:
- 获取原始bean,或者获取代理bean。
- 如果为了获取原始bean或者代理bean,把原始bean作为参数一直往下传递只为了在这个地方判断是否需要代理,会比较麻烦而且不必要,因此我们希望:
- 在缓存A中取出来的,可以智能一点,因此缓存A我们就用来存这个相关的内容:返回原始对象或者代理对象,这里就是Spring中的singletonFactories:<BeanNameA,工厂方法>
- 这里的工厂方法,代表的就是生成原始对象,或生成代理对象的方法。
-
-