目录
- 1.1.1 bean生命周期中重要接口
- 1.1.2 创建bean
- 1.1.3 属性填充
- 1.1.4.1 Aware相关接口
- 1.1.4.2 BeanPostProcessors相关接口
- 1.1.4.3 InitializingBean接口
- 1.1.4.4 BeanPostProcessors接口后置方法
- 1.1.5 bean生命周期总结
- 1.2.1 引言
- 1.2.2 三级缓存各个存放对象
- 1.2.3 解决循环依赖条件
- 1.2.4 循环依赖
- 1.2.5 是否可以移除二级缓存
1 Spring核心
在使用spring
框架的日常开发中,bean
之间的循环依赖太频繁了,spring
已经帮我们去解决循环依赖问题,对我们开发者来说是无感知的,下面具体分析一下spring
是如何解决bean之间循环依赖,为什么要使用到三级缓存,而不是二级缓存?
1.1 bean生命周期
首先大家需要了解一下bean
在spring
中的生命周期,bean
在spring
的加载流程,才能够更加清晰知道spring
是如何解决循环依赖的。
1.1.1 bean生命周期中重要接口
我们在spring
的BeanFactory
工厂列举了很多接口,代表着bean
的生命周期,我们主要记住的是圈红线圈出来的接口, 再结合spring
的源码来看这些接口主要是在哪里调用的
1.1.2 创建bean
AbstractAutowireCapableBeanFactory
类的doCreateBean
方法是创建bean
的开始,我们可以看到首先需要实例化这个bean
,也就是在堆中开辟一块内存空间给这个对象,createBeanInstance
方法里面逻辑大概就是采用反射生成实例对象,进行到这里表示对象还并未进行属性的填充,也就是@Autowired
注解的属性还未得到注入
1.1.3 属性填充
我们可以看到第二步就是填充bean
的成员属性,populateBean
方法里面的逻辑大致就是对使用到了注入属性的注解就会进行注入,如果在注入的过程发现注入的对象还没生成,则会跑去生产要注入的对象,第三步就是调用initializeBean
方法初始化bean
,也就是调用我们上述所提到的接口
1.1.4 初始化bean
可以看到initializeBean
方法中,首先调用的是使用的Aware
接口的方法,我们具体看一下invokeAwareMethods
方法中会调用Aware
接口的那些方法
1.1.4.1 Aware相关接口
我们可以知道如果我们实现了BeanNameAware
,BeanClassLoaderAware
,BeanFactoryAware
三个Aware
接口的话,会依次调用setBeanName(), setBeanClassLoader(), setBeanFactory()
方法,再看applyBeanPostProcessorsBeforeInitialization
源码
1.1.4.2 BeanPostProcessors相关接口
发现会如果有类实现了BeanPostProcessor
接口,就会执行postProcessBeforeInitialization
方法,这里需要注意的是:如果多个类实现BeanPostProcessor
接口,那么多个实现类都会执行postProcessBeforeInitialization
方法,可以看到是for
循环依次执行的,还有一个注意的点就是如果加载A类到spring
容器中,A类也重写了BeanPostProcessor
接口的postProcessBeforeInitialization
方法,这时要注意A类的postProcessBeforeInitialization
方法并不会得到执行,因为A类还未加载完成,还未完全放到spring
的singletonObjects
一级缓存中。
再看一个注意的点
可以看到ApplicationContextAwareProcessor
也实现了BeanPostProcessor
接口,重写了postProcessBeforeInitialization
方法,方法里面并调用了invokeAwareInterfaces
方法,而invokeAwareInterfaces
方法也写着如果实现了众多的Aware
接口,则会依次执行相应的方法,值得注意的是ApplicationContextAware
接口的setApplicationContext
方法,再看一下invokeInitMethods
源码
1.1.4.3 InitializingBean接口
发现如果实现了InitializingBean
接口,重写了afterPropertiesSet
方法,则会调用afterPropertiesSet
方法,最后还会调用是否指定了init-method
,可以通过标签,或者@Bean
注解的initMethod
指定,最后再看一张applyBeanPostProcessorsAfterInitialization
源码图
1.1.4.4 BeanPostProcessors接口后置方法
发现跟之前的postProcessBeforeInitialization
方法类似,也是循环遍历实现了BeanPostProcessor
的接口实现类,执行postProcessAfterInitialization
方法。整个bean
的生命执行流程就如上面截图所示,哪个接口的方法在哪里被调用,方法的执行流程。
1.1.5 bean生命周期总结
最后,对bean的生命流程进行一个流程图的总结
或者看简单版本:
1.2 三级缓存
1.2.1 引言
上面对bean
的生命周期做了一个整体的流程分析,对spring
如何去解决循环依赖的很有帮助。前面我们分析到填充属性时,如果发现属性还未在spring
中生成,则会跑去生成属性对象实例。
我们可以看到填充属性的时候,spring
会提前将已经实例化的bean
通过ObjectFactory
半成品暴露出去,为什么称为半成品是因为这时候的bean
对象实例化,但是未进行属性填充,是一个不完整的bean
实例对象
在实例化 Bean
之后,会往 singletonFactories
塞入一个工厂,而调用这个工厂的 getObject
方法,就能得到这个 Bean
spring
利用singletonObjects, earlySingletonObjects, singletonFactories
三级缓存去解决的,所说的缓存其实也就是三个Map
1.2.2 三级缓存各个存放对象
三级缓存各个存放对象:
-
一级缓存
,singletonObjects
,存储所有已创建完毕的单例 Bean (完整的 Bean) -
二级缓存
,earlySingletonObjects
,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean -
三级缓存
,singletonFactories
,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存
这三个 map 是如何获取配合的:
- 获取单例
Bean
的时候会通过BeanName
先去singletonObjects
(一级缓存) 查找完整的Bean
,如果找到则直接返回,否则进行步骤 2。 - 看对应的
Bean
是否在创建中,如果不在直接返回找不到,如果是,则会去earlySingletonObjects
(二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3 - 去
singletonFactories
(三级缓存)通过BeanName
查找到对应的工厂,如果存着工厂则通过工厂创建Bean
,并且放置到earlySingletonObjects
中。 - 如果三个缓存都没找到,则返回 null
可以看到三级缓存各自保存的对象,这里重点关注二级缓存earlySingletonObjects
和三级缓存singletonFactory
,一级缓存可以进行忽略。前面我们讲过先实例化的bean
会通过ObjectFactory
半成品提前暴露在三级缓存中
singletonFactory
是传入的一个匿名内部类,调用ObjectFactory.getObject()
最终会调用getEarlyBeanReference
方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。
1.2.3 解决循环依赖条件
在 Spring
中,只有同时满足以下两点才能解决循环依赖的问题:
- 必须是单例
依赖的Bean
必须都是单例
因为原型模式都需要创建新的对象,不能跟用以前的对象 - 不能全是构造器注入
依赖注入的方式,必须不全是构造器注入,且beanName
的字母顺序
在前的不能是构造器注入
在 Spring 中创建 Bean 分三步:实例化
,createBeanInstance,就是 new 了个对象属性注入
,populateBean, 就是 set 一些属性值初始化
,initializeBean,执行一些 aware 接口中的方法,initMethod,AOP代理等
明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。
如果全是构造器注入,比如A(B b)
,那表明在new
的时候,就需要得到 B,此时需要new B
。但是 B 也是要在构造的时候注入 A ,即B(A a)
,这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。
为什么找不到?因为 A 还没 new 完呢,所以找到不完整的 A,因此如果全是构造器注入的话,那么Spring
无法处理循环依赖 - 一个set注入,一个构造器注入能否成功
假设我们 A 是通过 set 注入 B,B 通过构造函数注入 A,此时是成功的
我们来分析下:实例化 A 之后,可以在 map 中存入 A,开始为 A 进行属性注入,发现需要 B,此时 new B,发现构造器需要 A,此时从 map 中得到 A ,B 构造完毕。
B 进行属性注入,初始化,然后 A 注入 B 完成属性注入,然后初始化 A。
整个过程很顺利,没毛病假设 A 是通过构造器注入 B,B 通过 set 注入 A,此时是失败的
我们来分析下:实例化 A,发现构造函数需要 B, 此时去实例化 B。
然后进行 B 的属性注入,从 map 里面找不到 A,因为 A 还没 new 成功,所以 B 也卡住了,然后就 失败
看到这里,仔细思考的小伙伴可能会说,可以先实例化 B 啊,往 map 里面塞入不完整的 B,这样就能成功实例化 A 了啊
确实,思路没错但是Spring 容器是按照字母序创建 Bean 的,A 的创建永远排在 B 前面
现在我们总结一下:
- 如果循环依赖都是构造器注入,则失败
- 如果循环依赖不完全是构造器注入,则可能成功,可能失败,具体跟
BeanName
的字母序有关系
1.2.4 循环依赖
我们假设现在有这样的场景AService
依赖BService
,BService
依赖AService
-
AService
首先实例化,实例化通过ObjectFactory
半成品暴露在三级缓存中 - 填充属性
BService
,发现BService
还未进行过加载,就会先去加载BService
- 再加载
BService
的过程中,实例化,也通过ObjectFactory
半成品暴露在三级缓存 - 填充属性
AService
的时候,这时候能够从三级缓存中拿到半成品的ObjectFactory
拿到ObjectFactory
对象后,调用ObjectFactory.getObject()
方法最终会调用getEarlyBeanReference()
方法,getEarlyBeanReference
这个方法主要逻辑大概描述下如果bean
被AOP
切面代理则返回的是beanProxy
对象,如果未被代理则返回的是原bean实例
。
这时我们会发现能够拿到bean
实例(属性未填充),然后从三级缓存移除
,放到二级缓存earlySingletonObjects
中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。
1.2.5 是否可以移除二级缓存
我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题
只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean
没被AOP
进行切面代理,如果这个bean
被AOP
进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean
被AOP
进行了切面代理的场景
我们发现AService
的testAopProxy
被AOP
代理了,看看传入的匿名内部类的getEarlyBeanReference
返回的是什么对象。
发现singletonFactory.getObject()
返回的是一个AService
的代理对象,还是被CGLIB
代理的。再看一张再执行一遍singletonFactory.getObject()
返回的是否是同一个AService
的代理对象
我们会发现再执行一遍singleFactory.getObject()
方法又是一个新的代理对象,这就会有问题了,因为AService
是单例的,每次执行singleFactory.getObject()
方法又会产生新的代理对象。
假设这里只有一级和三级缓存的话,每次从三级缓存中拿到singleFactory
对象,执行getObject()
方法又会产生新的代理对象,这是不行的,因为AService
是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()
产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()
方法再产生一个新的代理对象,保证始终只有一个代理对象。还有一个注意的点
既然singleFactory.getObject()
返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过CGLIB
代理的AService
对象。所以如果没有AOP
的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP
,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()
方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象