一道被难住的Spring面试题
面试官: Sring是如何解决循环引用的?
了不起: 提前暴露还没初始化完成的Bean,使用了三级缓存解决
面试官: 为什么要用三级,可以改成二级缓存吗?
Bean的初始化
首先看下Bean 的创建流程主要包括以下几个步骤:
实例化: Spring 根据 Bean 的配置信息创建一个实例。这个实例还没有进行属性注入和初始化。
属性注入: Spring 根据配置信息为 Bean 实例注入属性。这包括对其他 Bean 的引用和基本类型的值。
初始化: Spring 调用 Bean 的初始化方法,如 @PostConstruct 注解的方法、实现 InitializingBean 接口的 afterPropertiesSet 方法或者配置文件中指定的 init-method。
Bean 后置处理器: Spring 调用注册的 BeanPostProcessor 的 postProcessBeforeInitialization 方法,允许对 Bean 进行额外的处理。
应用代理: 如果 Bean 需要被代理(例如,使用 AOP 功能),Spring 会在这个阶段为 Bean 创建代理对象。
Bean 后置处理器:Spring 调用注册的 BeanPostProcessor 的 postProcessAfterInitialization 方法,允许对 Bean 进行额外的处理。
将 Bean 放入容器: 最后,Spring 将创建好的 Bean 放入容器中,供应用程序使用。
三级缓存
Spring 解决循环依赖问题的关键在于它的“三级缓存”机制。这个机制主要涉及以下三个缓存:
- singletonObjects:存已经完全初始化好的单例 Bean。
- earlySingletonObjects:存储提前暴露的、尚未完全初始化的单例 Bean。
- singletonFactories:存储用于创建单例 Bean 的工厂对象。
当 Spring 容器处理循环依赖时,会按照以下步骤进行:
- 创建 Bean A 的实例。
- 在注入 Bean A 的属性时,发现需要依赖 Bean B。
- 创建 Bean B 的实例。
- 在注入 Bean B 的属性时,发现需要依赖 Bean A。此时,Bean A 尚未完全初始化,但已经创建了实例。将 Bean A 的工厂对象存入 singletonFactories 缓存,并将 Bean A 的实例存入 earlySingletonObjects 缓存。
- 从 earlySingletonObjects 缓存中获取 Bean A 的实例,注入到 Bean 的属性中。
- 完成 Bean B 的初始化,将其存入 singletonObjects 缓存。
- 继续完成 Bean A 的属性注入,此时可以从 singletonObjects 缓存中获取已经初始化好的 Bean B 实例。
- 完成 A 的初始化,将其存入 singletonObjects 缓存。
上述核心步骤都存在于AbstractAutowireCapableBeanFactory
这个类下面。
设计原则
重点看doCreateBean
方法,省略部分代码,可以看到默认情况下earlySinletionExposure为true,所以每次Bean创建的都会把工厂对象放到三级缓存中。并且看到第二个参数是一个函数式接口,深入getEarlyReference的方法实现,可以发现工厂对象返回的是代理对象,
接着往下看,earlySinletionExposure为truee的情况下会调用getSingleton,注意到传递的第二参数allowEarlyReference为false。
getSingleton在allowEarlyReference为false的情况下,从earlySingletonObjects二级缓存中获取对象。此时二级缓存中存储的正是从三级缓存中获取的代理对象。
结论
Spring 使用三级缓存而不是二级缓存来解决循环依赖问题,主要是为了遵循其设计原则和保持创建代理对象的时机更加合理。
如果 Spring 选择二级缓存来解决循环依赖,那么所有 Bean 都需要在实例化完成之后就立马为其创建代理。然而,Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。这是因为 Spring 结合 AOP 和 Bean 的生命周期,在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。
为了实现提前暴露对象而又不生成代理,Spring 使用了三级缓存。三级缓存中存放的是 ObjectFactory,而不是实例化的 Bean。这样,在被注入时,ObjectFactory.getObject 方法内实时生成代理对象,并将生成好的代理对象放入到二级缓存中。这种设计让 Bean 的创建流程更加符合常理,更加清晰明了。
总之,虽然理论上使用二级缓存可以解决循环依赖问题,但 Spring 选择了三级缓存来实现,以保持创建代理对象的时机更加合理并遵循其设计原则。