一道被难住的Spring面试题

2023-10-24 18:13:49 浏览数 (1)

一道被难住的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 解决循环依赖问题的关键在于它的“三级缓存”机制。这个机制主要涉及以下三个缓存:

  1. singletonObjects:存已经完全初始化好的单例 Bean。
  2. earlySingletonObjects:存储提前暴露的、尚未完全初始化的单例 Bean。
  3. singletonFactories:存储用于创建单例 Bean 的工厂对象。

当 Spring 容器处理循环依赖时,会按照以下步骤进行:

  1. 创建 Bean A 的实例。
  2. 在注入 Bean A 的属性时,发现需要依赖 Bean B。
  3. 创建 Bean B 的实例。
  4. 在注入 Bean B 的属性时,发现需要依赖 Bean A。此时,Bean A 尚未完全初始化,但已经创建了实例。将 Bean A 的工厂对象存入 singletonFactories 缓存,并将 Bean A 的实例存入 earlySingletonObjects 缓存。
  5. 从 earlySingletonObjects 缓存中获取 Bean A 的实例,注入到 Bean 的属性中。
  6. 完成 Bean B 的初始化,将其存入 singletonObjects 缓存。
  7. 继续完成 Bean A 的属性注入,此时可以从 singletonObjects 缓存中获取已经初始化好的 Bean B 实例。
  8. 完成 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 选择了三级缓存来实现,以保持创建代理对象的时机更加合理并遵循其设计原则。

我是了不起
和我一起学习更多精彩知识!!!

0 人点赞