《Spring Boot 3:高级与架构设计》第 3 章 Bean 的全生命周期原理 Bean 的全生命周期概览 阅读笔记 9
3.1 Bean 的全生命周期概览
在入门篇中就已经讲过,Bean 的生命周期如图所示。对于普通的 Java 对象而言,图的描述完全成立、合理,但在 Spring Framework 中它却不够完整。首先回顾前面学习的 Bean 的基本生命周期。

在 bean 对象被创建之前,Bean 以 BeanDefinition 的方式注册于 BeanFactory(准确地说是注册于 BeanDefinitionRegistry),
此时BeanFactory 中已经存放了 Bean 对应的 BeanDefinition 信息,所以在 bean 对象的实例化之前,还应该有 BeanDefinition 阶段。
在第 1 章中已经了解到,BeanDefinition 不是凭空出现的,它的来源包括解析 XML 配置文件、解析注解配置类以及编程式注入,
所以纵观整个Bean 的生命周期,在 BeanDefinition 阶段之前还应该有 BeanDefinition 的来源和加载阶段。
所以,可以梳理出 Bean 的全生命周期包含两大环节。本章以单实例 Bean 的全生命周期为主线讨论。
(1)BeanDefinition 阶段:BeanDefinition 的解析、BeanDefinition 的注册。
(2)bean 对象阶段:bean 对象的实例化、bean 对象的属性赋值和依赖注入、bean 对象的初始化流程、bean 对象的启动与停止、bean 对象的销毁。
在正式开始源码解读和原理分析之前,先从概念和设计上对 Bean 的全生命周期有一个整体把控,带着问题和思考读源码更有助于理解。
3.1.1 BeanDefinition 的来源
Bean 的实例化通常需要 BeanDefinition 的信息,所以首先要梳理的是 BeanDefinition 的来源。在 1.3.3 节中已经简单列举了 BeanDefinition 的 3 种主要来源,具体如下。
1. 声明式注册
声明式注册 Bean 的 BeanDefinition,来源主要包括代码所示的 3 种方式:@Configuration + @Bean、@Component + @ComponentScan 以及利用 @Import。
java
@Configuration
public class QuickStartConfiguration{
@Bean
public Person person(){
return new Person();
}
}
@Configuration
@ComponentScan("com.yangjunbo.spring.annotation.bean")
public class ComponentScanConfiguration
@Import({Boss.class,BartenderConfiguration.class})
public @interface EnableTavern{
...
}
2. 配置式注册
配置式注册通过 XML 配置文件完成,如代码所示。严格来讲,配置式注册也属于声明式注册。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.yangjunbo.spring.definition.examplea.Person">
<property name="name" value="yangjunbo"/>
</bean>
</beans>
3. 编程式注册
编程式注册均需直接或间接依赖 Spring Framework 的 BeanDefinitionRegistry,如代码所示。
目前已经学过的注册方式主要有借助 ImportBeanDefinitionRegistrar、借助 BeanDefinitionRegistryPostProcessor 以及获取 IOC 容器后直接调用 BeanDefinitionRegistry 接口中的方法。
java
public class WaiterRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("waiter", new RootBeanDefinition(Waiter.class));
}
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
BeanDefinition personDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).addPropertyValue("name", "老王").getBeanDefinition();
ctx.registerBeanDefinition("laowang", personDefinition);
ctx.refresh();
public class AnimalProcessorRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("animalNameSetterPostProcessor", new RootBeanDefinition(AnimalNameSetterPostProcessor.class));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
3.1.2 BeanDefinition 的后置处理
通过第 2 章的学习,不难总结出一条非常关键的结论:BeanDefinition 构造完毕后被放入 BeanDefinitionRegistry 的动作时机并不完全确定。
在 IOC 容器刷新的过程中,BeanDefinition 必须经历 BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor 的处理,才会真正地被注册到 BeanDefinitionRegistry 中。2.3 节已经详细讲解过 BeanFactoryPostProcessor 与 BeanDefinitionRegistryPostProcessor 的切入时机与职责:BeanDefinitionRegistryPostProcessor 率先执行,可以对 BeanDefinitionRegistry 中的 BeanDefinition 进行修改、移除,此外还可以注册新的 BeanDefinition;BeanFactoryPostProcessor 随后执行,原则上只能对 BeanDefinitionRegistry 中现有的 BeanDefinition 进行修改和移除,不建议再注册新的 BeanDefinition,如图所示。

3.1.3 Bean 的实例化阶段
结合 IOC 容器的抽象实现 AbstractApplicationContext 中的核心方法 refresh 可以得知,在刷新 IOC 容器的过程中,finishBeanFactoryInitialization 方法将逐一创建所有尚未实例化的、非延迟加载的单实例 Bean。其流程多且复杂,可以分为以下 5 步。
1. BeanDefinition 的合并
Bean 的实例化阶段第一步并不是直接创建对象。1.3.4 节曾介绍过 BeanDefinition 的合并特性,以 XML 配置式注册 Bean 为例,如果一个继承另外一个定义,就会形成父子定义,这种情况下在创建 Bean 之前需要读取父 Bean 的定义,将其合并为最终的 RootBeanDefinition,之后才能进行对象创建。
2. bean 的实例化
合并 BeanDefinition 之后,第二步才是真正的实例化 bean 动作。该环节有一个问题需要读者注意:如果被创建的 bean 是一个 FactoryBean,IOC 容器在实例化时会有什么针对性的策略?是仅创建 FactoryBean 本身,还是连 FactoryBean 对应创建的真实 Bean 一起创建?对这个问题此处先不作回答,进入源码解读时自有答案。
除了 FactoryBean,还要考虑缓存问题。在入门篇中已经了解到 BeanFactory 内部可以缓存单实例 Bean,那么 IOC 容器对实例化后的单实例Bean 又是如何处理和放入缓存的?
3. 属性赋值 + 依赖注入
Bean 实例化后,内部所有需要被注入的属性均为空,接下来应进行属性赋值和依赖注入。由于 bean 已经创建完毕,因此笔者将该步骤归入下述 Bean 的初始化。
重要的是研究如何处理 Bean 中声明的 @Value、@Autowired 等与属性注入相关的注解。读者不妨先自行简单思考:现有机制能否在 bean 的初始化阶段之前完成这部分工作。
答案不难推测,BeanPostProcessor 的扩展 InstantiationAwareBeanPostProcessor 可以在 bean 对象实例化之后、初始化之前回调执行postProcessAfterInstantiation 方法,以及回调执行 postProcessProperties(postProcessPropertyValues)方法来完成属性赋值,所以我们可以大胆下结论,属性赋值和依赖注入的工作应当由 InstantiationAwareBeanPostProcessor 完成。
4. Bean 的初始化
Spring Framework 在 Bean 的生命周期中提供了回调扩展点,包括初始化回调和销毁回调,程序员可以根据需求自定义初始化和回调方法,在 Bean 的创建过程中,会调用初始化回调方法。
5. Bean 的启动
在入门篇的 3.4.7 节中,补充了 Bean 生命周期的扩展接口 Lifecycle。该接口的生命周期介入时机与 Bean 的初始化不同,实现 Lifecycle 接口的 Bean 被认为具备"可启动"和"可停止"的特性。因此,在实现 Lifecycle 接口的 Bean 中,其 start 方法通常用于建立连接、加载资源等操作,以备程序运行期使用。Lifecycle 接口的执行会跟随 ApplicationContext 的生命周期进行,两者紧密关联。
Lifecycle 的触发条件通常是显式调用 ApplicationContext 的 start 与 stop 方法。调用后,ApplicationContext 会自动找出所有实现 Lifecycle 接口的 bean 对象,并调用它们的 start 方法。此时,所有实现 Lifecycle 接口的非延迟加载单实例 Bean 均已加载完成,可以正常触发 start 方法。对于这类 Bean 而言,其生命周期已经进入正常存活期。
小提示:ApplicationContext 的 start 方法一旦被调用,除了触发 Lifecycle 类型 Bean 的启动逻辑,还会广播 ContextStartedEvent 事件。这也是一个可供扩展的切入点。
3.1.4 Bean 的销毁阶段
相较于实例化和初始化,Bean 的销毁阶段逻辑简洁。可以先了解 ApplicationContext 的关闭。关闭 ApplicationContext 会顺序执行以下5步,第2、3 步涉及 Bean 的生命周期。
(1)广播容器关闭事件ContextClosedEvent。
(2)通知所有实现了 Lifecycle 的 Bean 回调其 close 方法。
(3)销毁所有存放在 IOC 容器中的单实例 Bean。
(4)关闭 IOC 容器内部组合的 BeanFactory。
(5)标记 IOC 容器本身为不可用状态。
1. Bean 的停止
与 3.1.3 节中 Bean 的启动一致,如果一个 Bean 实现了 Lifecycle 接口,那么当 IOC 容器关闭时会回调这些 Bean 的 close 方法。
由于此时 Bean 的销毁回调方法尚未执行,因此在销毁阶段 Lifecycle 的执行时机相对靠前。
通常来讲,实现 Lifecycle 的 close 方法多用来关闭连接、释放资源等,因为程序已经停止,这些资源也相应地应当被回收和释放。
2. Bean 的销毁回调
所有实现 Lifecycle 接口的 bean "停止" 后,进入 Bean 生命周期回调的销毁阶段。与 Bean 的初始化不同,BeanPostProcessor 的回调时机包含bean 的初始化前后,但 DestructionAwareBeanPostProcessor 只包含 bean 销毁回调之前的动作,没有之后的动作。
3.1.5 Debug 测试代码编写
接下来简单编写用于探究本章原理内容的测试代码,如果没有特殊声明,本章使用的测试代码均来自本节内容。由于 Bean 的来源多样,在测试和研究时将分别使用 XML 配置文件与注解驱动两种方式展开。
1. 测试模型类
首先制作一套尽可能覆盖全生命周期关键环节的测试模型,创建两个类:Person 与 Cat。其中 Person 实现生命周期逻辑,Cat 仅使用属性赋值和自动注入的注解标注,并标注 @Component 即可,如代码所示。


java
package com.yangjunbo.spring.lifecycle.examplea.bean;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
public class Person implements InitializingBean, DisposableBean, Lifecycle {
private String name;
private boolean state = false;
public Person() {
System.out.println("Person constructor run ......");
}
@PostConstruct
public void postConstruct() {
System.out.println("Person @PostConstruct run ......");
}
@Override
public void afterPropertiesSet() {
System.out.println("Person InitializingBean run ......");
}
public void initMethod() {
System.out.println("Person initMethod run ......");
}
@PreDestroy
public void preDestory() {
System.out.println("Person @PreDestroy run ......");
}
@Override
public void destroy() {
System.out.println("Person DisposableBean run ......");
}
public void destroyMethod() {
System.out.println("Person destroyMethod run ......");
}
@Override
public void start() {
System.out.println("Person 睡醒起床了 ......");
this.state = true;
}
@Override
public void stop() {
System.out.println("Person 睡觉去了 ......");
this.state = false;
}
@Override
public boolean isRunning() {
return state;
}
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
java
package com.yangjunbo.spring.lifecycle.examplea.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Cat {
@Value("miaomiao")
private String name;
@Autowired
private Person master;
@Override
public String toString() {
return "Cat{" + "name='" + name + '\'' + ", master=" + master + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getMaster() {
return master;
}
public void setMaster(Person master) {
this.master = master;
}
}
2. 添加后置处理器
为了能体现出后置处理器的切入回调时机,下面分别编写一个 BeanPostProcessor 和一个 DestructionAwareBeanPostProcessor,如代码所示。
用于初始化阶段的 LifecycleNameReadPostProcessor 仅打印 Person 类型 Bean 的名称,而用于销毁阶段的 LifecycleDestructionPostProcessor 则针对 Cat 类型模拟一个放走的动作。


java
package com.yangjunbo.spring.lifecycle.examplea.config;
import com.yangjunbo.spring.lifecycle.examplea.bean.Person;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LifecycleNameReadPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Person) {
Person person = (Person) bean;
System.out.println("LifecycleNameReadPostProcessor ------> " + person.getName());
}
return bean;
}
}
java
package com.yangjunbo.spring.lifecycle.examplea.config;
import com.yangjunbo.spring.lifecycle.examplea.bean.Cat;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
public class LifecycleDestructionPostProcessor implements DestructionAwareBeanPostProcessor {
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
if (bean instanceof Cat) {
Cat cat = (Cat) bean;
System.out.println(cat.getName() + "被放走了 ......");
}
}
}
3. XML 配置文件与测试启动类
用于测试驱动 IOC 容器的 XML 配置文件中需要注册 person、cat 以及两个后置处理器,同时还需要开启注解驱动(否则 JSR-250 规范的注解将不会生效),如代码所示。

xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="person" class="com.yangjunbo.spring.lifecycle.examplea.bean.Person"
init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="zhangsan"/>
</bean>
<bean id="cat" class="com.yangjunbo.spring.lifecycle.examplea.bean.Cat">
<property name="name" value="mimi"/>
<property name="master" ref="person"/>
</bean>
<bean class="com.yangjunbo.spring.lifecycle.examplea.config.LifecycleNameReadPostProcessor"/>
<bean class="com.yangjunbo.spring.lifecycle.examplea.config.LifecycleDestructionPostProcessor"/>
<context:annotation-config/>
</beans>
编写测试启动类时需要注意两个细节:
一是构造 ClassPathXmlApplicationContext 时不能直接将 XML 配置文件作为参数传入构造方法,否则会自动触发 IOC 容器的刷新动作,要手动控制 IOC 容器刷新;
二是在调用 ApplicationContext 的方法时需要先执行 refresh,再执行 start 方法以启动 IOC 容器,随后就可以正常获取 IOC 容器的 Bean,而销毁 IOC 容器时应先调用 stop 方法再调用 close 方法。完整代码如下所示。

java
package com.yangjunbo.spring.lifecycle.examplea;
import com.yangjunbo.spring.lifecycle.examplea.bean.Cat;
import com.yangjunbo.spring.lifecycle.examplea.bean.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LifecycleSourceXmlApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.setConfigLocation("lifecycle/examplea/bean-source.xml");
System.out.println("================准备刷新IOC容器==================");
ctx.refresh();
System.out.println("================IOC容器刷新完毕==================");
ctx.start();
System.out.println("================IOC容器启动完成==================");
Person person = ctx.getBean(Person.class);
System.out.println(person);
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
System.out.println("================准备停止IOC容器==================");
ctx.stop();
System.out.println("================IOC容器停止成功==================");
ctx.close();
}
}

4. 注解配置类与测试启动类
相较于 XML 配置文件,注解驱动的配置类更简单,只需要开启组件扫描和注册 Person 对象即可,如代码所示。
对应的测试启动类也不难编写:与构造 ClassPathXmlApplicationContext 类似,在构造 AnnotationConfigApplicationContext 时不能直接将配置类作为参数传入构造方法,而是需要调用 AnnotationConfigApplicationContext 的 register 方法,手动注册注解配置类和后置处理器。

java
package com.yangjunbo.spring.lifecycle.examplea.config;
import com.yangjunbo.spring.lifecycle.examplea.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.yangjunbo.spring.lifecycle.examplea.bean")
public class LifecycleSourceConfiguration {
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public Person person() {
Person person = new Person();
person.setName("lisi");
return person;
}
}

java
package com.yangjunbo.spring.lifecycle.examplea;
import com.yangjunbo.spring.lifecycle.examplea.bean.Cat;
import com.yangjunbo.spring.lifecycle.examplea.bean.Person;
import com.yangjunbo.spring.lifecycle.examplea.config.LifecycleDestructionPostProcessor;
import com.yangjunbo.spring.lifecycle.examplea.config.LifecycleNameReadPostProcessor;
import com.yangjunbo.spring.lifecycle.examplea.config.LifecycleSourceConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class LifecycleSourceAnnotationApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(LifecycleSourceConfiguration.class);
ctx.register(LifecycleNameReadPostProcessor.class);
ctx.register(LifecycleDestructionPostProcessor.class);
System.out.println("================准备刷新IOC容器==================");
ctx.refresh();
System.out.println("================IOC容器刷新完毕==================");
ctx.start();
System.out.println("================IOC容器启动完成==================");
Person person = ctx.getBean(Person.class);
System.out.println(person);
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
System.out.println("================准备停止IOC容器==================");
ctx.stop();
System.out.println("================IOC容器停止成功==================");
ctx.close();
}
}
