Skip to main content

生命周期

David LiuAbout 7 min

生命周期

三个阶段

截屏2023-04-05 22.11.05

Spring Bean 的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到 Bean 成为一个完整对象,最终存储到单例池中,这个过程被称为 Spring Bean 的生命周期。Spring Bean 的生命周期大体上分为三个阶段:

  • Bean 的实例化阶段:Spring 框架会取出 BeanDefinition 的信息进行判断当前 Bean 的范围是否是 singleton 的是否不是延迟加载的,是否不是 FactoryBean 等,最终将一个普通的 singleton 的 Bean 通过反射进行实例化:
  • Bean 的初始化阶段:Bean 创建之后还仅仅是个"半成品",还需要对 Bean 实例的属性进行填充、执行一些 Aware 接口方法、执行 BeanPostProcessor,方法、执行 InitializingBean 接口的初始化方法、执行自定义初始化 init 方法等。该阶段是 Spring 最具技术含量和复杂度的阶段,Aop 增强功能,后面要学习的 Springl 的注解功能等、spring 高频面试题 Bean 的循环引用问题都是在这个阶段体现的:
  • Bean 的完成阶段:经过初始化阶段,Bean 就成为了一个完整的 Spring Bean,被存储到单例池 singletonObjects 中去了,即完成了 Spring Bean 的整个生命周期。
  • 销毁阶段

实例化阶段

BeanDefinition 对象,封装 bean 的基本信息,有一个 beanDefinitionMap 存这些 BeanDefinition。

BeanFactory 存两个东西:

  1. BeanDefinition 对象

  2. singletonObjects 单例池(最终的对象)

    在 DefaultSingletonBeanRegistry 类中

步骤:

  • 加载 xml 配置文件,解析获取配置中的每个<bean>的信息,封装成一个个的BeanDefinition对象。

  • BeanDefinition存储在一个名为beanDefinitionMapMap<String,BeanDefinition>中;

    key 是 bean 的 id

  • ApplicationContext底层遍历beanDefinitionMap,创建 Bean 实例对象。

    根据 BeanDefinition 中的全限定类名反射创建。

  • 创建好的 Bean 实例对象,被存储到一个名为singletonObjectsMap<String,Object>中。

    key 是 bean 的全限定类名

  • 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配 Bean 实例返回。

初始化阶段

Spring Bean 的初始化过程涉及如下几个过程:

  • Bean 实例的属性填充
  • Aware 接口属性注入
  • BeanPostProcessor 的 before() 方法回调
  • InitializingBean 接口的初始化方法回调
  • 自定义初始化方法 init 回调
  • BeanPostProcessor 的 after() 方法回调

属性注入

  • 普通属性,反射调用 set 方法
  • 注入单向对象引用,如果容器里有,则直接注入,否则先生成这个对象,再注入。
  • 注入双向对象引用,涉及到循环引用问题

循环依赖

需要把半成品先存起来注入

三级缓存存储不同状态的 Bean

  • singletonObjects: 完整 bean

  • earlySingletonObjects 二级缓存,还没创建完毕就存了,已经被引用的半成品。

  • singletonFactories: 三级缓存,单例工厂,未被引用的半成品

  • singletonObjects:单例池,也称之为一级缓存。

  • earlySingletonObjects:未完成初始化的单例池,也称之为二级缓存 。存已经被引用的半成品。

  • singletonFactories:三级缓存,实际上存放“创建对象的 lambda 表达式”。存未被引用的半成品。

    如果是普通对象直接返回,如果需要 AOP 则执行 lambda 表达式创建一个代理对象。

    • 原始对象:未经过完整生命周期的 bean。(半成品)
    • 代理对象:如果 Spring 扫描时发现某个对象需要切面,Spring 最终会经过 AOP 为其创建一个代理对象,并放入单例池中。)

解决 Spring 循环依赖问题只用一二级缓存行不行?

**场景:**如果 A 的原始对象注入给 B 的属性之后,A 的原始对象进行了 AOP(按照 bean 的生命周期,AOP 是在初始化后在后置处理器中处理的),此时会产生另外一个对象-代理对象,这个对象最终会被存放到单例池 singletonObjects 中,也就是说,对于 A 而言,它最终的 bean 对象实际上应该是 AOP 之后的代理对象而不是原来那个原始对象,但 B 拿的是原始对象,这就产生冲突:B 依赖的 A 和最终的 A 不是同一个对象。

那么如何解决 B 依赖的 A 和最终的 A 不是同一个对象这种冲突?

方式 1:不管有没有循环依赖问题,统一在初始化前 AOP?这种做法明显破坏了 Spring 所设定的 bean 生命周期,如果为了解决循环依赖问题去破坏 bean 生命周期的设计原则,那么得不偿失。

方式 2:判断是否依赖的对象也在创建,如果是才提前对该对象进行 AOP,并将 AOP 代理对象存放起来,后续该对象在初始化后,判断如果对象已被代理过,是则不再进行 AOP。

对比发现显然第二种方式更加合适,Spring 也确实采用了第二种方式来解决冲突,于是三级缓存singletonFactories派上用场**。就像其名一样,singletonFactories 是一个单例工厂。如果我们打开 Spring 源码,可以看到它的值存的是一个 beanName → lambda 表达式,这个lambda 表达式可能用到也可能用不到**。

  • 何时存入 lambda 表达式?

存入步骤是在创建完原始对象之后执行的,Spring 中是在 AbstractAutowireCapableBeanFactory.doCreateBean() → getSingleton() 中进行存储。

  • 什么时候执行 lambda 表达式?

获取 bean 的逻辑,走的是 DefaultSingletonBeanRegistry.getBean(),当 getBean 调用到 getSingleton() 时,会进入下面代码。从代码可以看出,只有当 1 级、2 级缓存拿不到 bean 时,且存在循环依赖问题(通过代码 isSingletonCurrentlyInCreation(beanName)判断)时,才会去执行 lambda 表达式,获取真正的 bean(普通原始对象或代理对象),并将其放入二级缓存 earlySingletonObjects 中,同时删除三级缓存中的引用(注意:此处因为需要保证原子性,前面对 singletonObjects 加了独占锁)。

  • lambda 中的逻辑是啥?

同样是在 AbstractAutowireCapableBeanFactory 类中,可以从 getEarlyBeanReference 方法中看到,最终 lambda 表达式执行的逻辑,就是去遍历所有后置处理器,然后在 SmartInstantiationAwareBeanPostProcessor 的实现类中的 getEarlyBeanReference 方法中去创建 bean,而创建的 bean 可能是普通原始对象 (在 InstantiationAwareBeanPostProcessorAdapter 中,直接返回当前 bean),也可能是代理对象(也就是提前 AOP,在 AbstractAutoProxyCreator 中通过 jdk 动态代理或 cglib 创建),需要注意的是无论是普通对象还是代理对象,都不是经过完整生命周期的最终对象,因此在执行 lambda 表达式后不能将返回的 bean 直接放入一级缓存,即单例池 singletonObjects 中,而是要先放到二级缓存提前暴露给其他 bean,只有经过所有后置处理器 BeanPostProcesssor 后才会真正放入 singletonObjects 中。

  • 如果 bean 被提前 AOP,初始化后的 AOP 逻辑中,要如何判断不需再进行 AOP?

当 bean 初始化后,后置处理器 AbstractAutoProxyCreator 中的 postProcessAfterInitialization 方法会被调用,其中存在这么一段逻辑,即判断 earlyProxyReferences(又是一个 cache,但一般不会把它计入三级缓存中)中是否已有该 cache,有则说明已经进行过 AOP,那么就不会再次进行 AOP。(wrapIfNecessary 中还有更多细节的判断是否需要 AOP,此处相当于提前截断,不需要再进行细节判断了)

什么时候下 Spring 解决不了循环依赖?

  • 情况一:使用构造器注入

Spring 在创建原始对象的原理也是调用构造器进行创建,如果使用构造器进行注入,那么 Spring 自身无法解决循环依赖问题,此时加入@Lazy 注解即可,原理是对于 Lazy 修饰的对象,Spring 会先创建一个代理对象给属性赋值,那么依赖方就可以正常进行实例化了。

  • 情况二:循环依赖且用@Async 注解修饰方法

Spring 在扫描 bean、发现某个类方法被@Async 修饰时,会通过后置处理器 AsyncAnnotationBeanPostProcessor 生成代理对象,而该后置处理器的顺序比处理 AOP 的后置处理器还靠后,因此仍然会导致 Spring 处理不了循环依赖。同理,可以使用@Lazy 注解解决该问题。

https://www.jianshu.com/p/abe87172c0d4open in new window