前言
⚠️⚠️⚠️
本文超级长,文字密集恐惧症请避让
自打从事java开发工作开始,很多人只关注于项目本身的运行,尽管知道了项目是一个Spring框架的项目,但是也没有真正去了解过具体的实现,也没有想过为什么我们编写了一个.java文件后,再点一个运行就能帮我们完成程序的运行。带着一些思考,看看你有没有下面的这些情况?
- 对于项目启动的控制台日志,从来没关注过,只关注过启动结果是否正常。
- 运行时候报错,一看到Spring中相关代码就两眼一黑。
- 让你去扩展一下Spring中的功能,不知道从何下手。
本文虽然说的是Spring的中的bean的生命周期,但是全文会从更高的角度去分析这个bean到底是怎么玩的,为什么会出现这种情况。
Spring中的Bean的生命周期,本文的目录总览
- 核心生命周期阶段
- 扩展点与回调接口
- 特殊作用域的生命周期
- 生命周期中的AOP处理
1. 详细文章结构
Spring Bean生命周期概述
什么是Bean生命周期
我们先理解一下生命周期这个词,用人去类比一下, Spring容器中的Bean从出生到死亡的全过程,就像人的一生:
- 出生(创建):Spring用构造方法把你"生出来"(实例化)
- 上户口(属性设置):给你起名字(BeanName)、分配资源(依赖注入)
- 上学培训(初始化):调用你的初始化方法(类似岗前培训)
- **工作(使用期):**正式干活(处理业务请求)
- **退休(销毁):**容器关闭时调用你的销毁方法(类似办理退休手续)
Bean在Spring容器中也会有他的一生,总的来看,它被我们所创建,我们用完了再把他进行销毁。其中的各个阶段就是Bean生命中的生命节点。和人一样,每一个生命节点对人的发展都很重要。
为了更好地理解,我们用一张图来了解Bean的整个生命周期。
图中我简化了整个生命链路,并在连接线中写了一些基本的操作动作。
容器如何管理生命周期
那么在我们知道了Bean的生命周期是什么后,那我们思考一个问题,人的成长是有一定的环境,人在社会中成长,也正是因为这个社会环境才是人能成长的前提,那么对应到Bean中去呢?没错,Bean就是在Spring中容器中才有了成长的生命周期。首先,Spring容器又是什么,容器这个概念我相信大家应该都不陌生, 那么Spring容器呢?我们在通过一个例子进行对比下。
- Spring 容器就像个智能幼儿园:
- 园长:负责管理所有小朋友(Bean)的入园到毕业
- 花名册:记录每个小朋友的档案(配置信息)
- 保育员:专门照顾小朋友的日常生活(依赖注入、生命周期回调)
注:括号内是类比实际的Bean在Spring容器中的行为
我们看下小朋友在小朋友在幼儿园的一天(生命周期流程)
结合上述图片我们分别对图片中的每个节点进行说明,并类比到Bean实例中去。
- 入园登记(实例化)
- 园长叫名字:“小明!”(根据配置创建Bean)
- 新生领书包水杯(构造方法初始化)
- 分配座位(依赖注入)
- 老师安排同桌:“小明和小红坐一起”(@Autowired注入)
- 发放文具(设置属性值)
- 晨检(初始化)
- 保健老师检查(@PostConstruct)
- 系好鞋带(InitializingBean回调)
- 确认健康状态(BeanPostProcessor处理)
- 上课活动(使用期)
- 正常上课学习(处理业务逻辑)
- 单例小朋友:一直用同一个(singleton)
- 原型小朋友:每次活动换新人(prototype)
- 放学回家(销毁)
- 老师检查书包(@PreDestroy)
- 和家长交接(DisposableBean回调)
- 挥手再见(资源释放
通过上述幼儿园类比的案例,我相信大家已经对Bean生命周期以及,Spring容器是如果管理Bean生命周期已经有了大致的了解。同时对其中的一些专业的类名和名词有了一点点的印象,如果在下文中出现了一样的名词,请不要惊讶。
标准生命周期阶段详解
相信大家在看完幼儿园的的案例后,是对Bean生命周期有一定的了解,那么我们还是用幼儿园类比一下什么是标准生命周期的阶段详解。在开始前我们思考一下“标准”这个词,什么是标准?他是不是就是给Bean的定制了一套规则,在什么阶段Bean就需要干什么事情,然后我们把这一套规则应用于每一个Bean实例中,让他们按照我们这一套规则来进行成长。而这样的一套规则就是标准。我们直接看以下例子:
- 入园报到(实例化阶段)
- 就像新生入学登记:
- 园长查名单:根据班级名单(配置)点名
- 发学生证:new Student() 创建对象实例
- 典型问题:双胞胎取名冲突(重复Bean定义报错)
- 分配座位(属性赋值阶段)
- 老师安排教室座位:
- 同桌分配:@Autowired private DeskMate deskMate;
- 发课本:setTextbook(new MathBook())
- 特殊照顾:残疾同学配拐杖(特殊依赖处理)
- 入学体检(初始化阶段)
- 分三步检查:
- 打疫苗:@PostConstruct public void init()
- 体检表:InitializingBean.afterPropertiesSet()
- 班主任评语:BeanPostProcessor.postProcessBeforeInitialization()
- 上课学习(使用期)
- 正常教学活动:
- 单例学生:整个幼儿园期间就这1个(默认)
- 原型学生:每节课都克隆新同学(@Scope(“prototype”))
- 课堂纪律:AOP代理像班长,管理方法调用
- 毕业离校(销毁阶段)
- 放学前的收尾工作:
- 收拾书包:@PreDestroy public void clear()
- 交还学生证:DisposableBean.destroy()
- 值日生打扫:数据库连接池等资源释放
还是和之前一样,我们在每个动作后面加入了实际的Bean的一些操作,用于关联真是的场景。
接下来的内容可能就会配合相关代码展示来进行更好的说明。
1. 实例化阶段
我们在上面已经说完了Bean的生命周期,以及容器是如果管理Bean的生命周期,那么现在就要深入Bean的生命周期开始,从第一个阶段开始,也就是实例化阶段。可以看下上面的幼儿园的例子第一个行为就是实例化。
在先介绍实例化之前,我们先看先看一下一个关键的类BeanDefinition,那这个类的作用什么尼,你有没有想过为什么我们在java的类上加上一个@Service,或者在XML文件中配置一个“bean”标签就能把相应的java文件加载成bean?其中的BeanDefinition这个类可是发挥了重大的作用。我们简要的看下BeanDefinition类中的属性。
// 典型 BeanDefinition 包含的信息
public interface BeanDefinition {
String getBeanClassName(); // 类名(不是文件转换)
String getScope(); // 作用域
boolean isLazyInit(); // 是否懒加载
ConstructorArgumentValues getConstructorArgumentValues(); // 构造参数
MutablePropertyValues getPropertyValues(); // 属性值
}
看到上面的代码你是不是就豁然开朗,没错,他的作用就是存储类基础信息+实例化规则的元数据集合。
如果我们想加载一个类,是不是只要获取BeanDefinition中的className,然后再用类加载器就能加载我们的类,有了这个BeanDefinition是不是对我们的加载类和实例化有了很大的帮助?当然我们只是对这个类做了最简单的阐述,目的就是让我们大致了解他的作用是啥。
接下来我们知道了实例化的必要条件后再看下什么是实例化,他该怎么解释,以及它的作用啥?
- 什么是实例化?
实例化就是把Java类的Class定义变成真正的对象的过程,相当于用"设计图"造出"实物"。
- 通俗类比
- 类 = 汽车设计图(包含所有参数和功能描述)
- 实例 = 来的真实汽车(可以实际驾驶)
// 设计图
public class Car {
private String model;
//...
}
// 实例化(造车)
Car myCar = new Car(); // ← 这就是实例化
相信你看了上述的例子之后,就应该能明白,实例化是什么,以及他的作用是什么,显而易见,就是把我们写的java文件,转换成对应的实际对象,并保存在内存中。
可以看下下面的详细的表格,用于阐述实例化的核心的作用
注意:还有一个关键的点,就是内存的变化。
// 实例化前:只有Class定义(方法区)
Class carClass = Car.class;
// 实例化后:堆内存出现对象
Object carInstance = carClass.newInstance();
以上内容就是Bean的实例化已经完成,下面就会介绍了Bean的属性填充,我们还是会用案例进行解释,知其然知其所以然。
2. 属性赋值阶段
依赖注入过程
再完成实例化之后,下一步就是属性注入(依赖注入)。这是 Spring Bean 生命周期中的关键步骤。在上面的实例化回调案例中,我们说到,虽然使用了@Autowired注入了userRepository, 但是此时全部是null
@Component
public class UserService {
@Autowired // 👈 属性注入在这里发生(实例化之后!)
private UserRepository userRepository;
public UserService() {
// 实例化时(构造方法中),userRepository 还是 null!
System.out.println(userRepository); // 输出 null
}
}
让我来解释一下:
- 实例化:相当于 new Object(),只是创建了一个"空壳"对象(此时对象内的 @Autowired 字段全是 null)。
- 属性注入:Spring 给这个空壳对象填内容(自动赋值 @Autowired 的字段、@Value 的属性等)
在这里,我在简单介绍说一下属性注入的底层流程,就不做具体的代码深究了。
-
Spring 在实例化后,会通过以下步骤完成属性注入:
-
扫描所有字段/方法:找到带 @Autowired、@Value 等注解的成员。
-
解析依赖:
- 如果是其他 Bean(如 UserRepository),从容器中查找并注入。
- 如果是配置值(如 @Value(“${db.url}”)),从配置文件中读取。
- 反射赋值:通过反射直接修改对象的字段值(即使字段是 private 的!)
获取你还在你日常的代码中看到过这种代码:
@Component
public class UserService {
private final UserRepository userRepository;
// 构造器注入:实例化时直接传参(属性注入的另一种形式)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository; // 此时依赖已注入!
}
}
你会发现这种注入方法没有用@Autowired注解,但是也能获得一样的效果,这个其实是另一种的注入方式-构造器注入。
简单对比一下两种构造,不做深究
- 构造器注入:实例化时直接完成依赖注入(一步到位)。
- 属性注入:实例化后,再通过反射赋值(多一步骤)。
到这里,Bean的实例化已经完全介绍完毕了,这里也包括属性注入,这些都是为了Bean实例化做铺垫。下面就会相信介绍下Bean的初始化,在Bean的生命周期中是非常重要的。
Aware接口回调
在介绍前Bean初始化之前,还有一个操作就是对Aware接口的回调,要说明的是即使不实现 Aware 接口,Bean 也能正常初始化。同时Aware先关类发挥的只是辅助功能,目的是让 Bean 能主动获取容器信息(属于锦上添花的功能)。比如:
@Component
public class Employee implements BeanNameAware, ApplicationContextAware {
private String beanName;
private ApplicationContext ctx;
@Override // BeanNameAware 回调
public void setBeanName(String name) {
this.beanName = name; // 被注入Bean ID
}
@Override // ApplicationContextAware 回调
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx; // 被注入容器实例
}
@PostConstruct
public void init() {
System.out.println("Bean ["+beanName+"] 初始化完成");
}
}
这里的Aware回调接口我们不做多阐述,我们只需要知道他是干嘛的就行,重点还是放在下面的初始化上。
3. 初始化阶段
初始化步骤总结
为了方便我们的理解,首先我们还是先介绍一下什么是初始化,初始化的作用是啥。
- 我们还是举一个例子:
初始化就像新手机开机设置: - 手机出厂(实例化) ≠ 能直接用
- 需要:连WiFi、登录账号、安装APP(初始化) → 才能正常使用
在Spring中:
初始化 = Bean 完成实例化 和 依赖注入后,容器对其进行的最后配置阶段
BeanPostProcessor前置处理
在真正的查看源码中Bean中初始化的代码:
// AbstractAutowireCapableBeanFactory
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// 1. 执行前置处理
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 2. 执行初始化回调
invokeInitMethods(beanName, wrappedBean, mbd);
// 3. 执行后置处理
return applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
可以看到在真正初始化的时候,是有一个之前前置处理的。顾名思义就是在初始化的时候,做一些事情。看下详细的实现代码。
// AbstractAutowireCapableBeanFactory
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
// 每个处理器都可能修改或替换 Bean
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) return result;
result = current;
}
return result;
}
很显然它只是做了一下其他的操作,并没有涉及到初始化相关操作,因此它不是初始化的必要条件,但它是 Spring 提供的一个强力扩展点。我举一个例子就知道了,大名鼎鼎的AOP就是在此代理生成的。
InitializingBean.afterPropertiesSet()
在进行了初始化的前置增强后,后就是执行InitializingBean回调,先看代码。
// 典型实现
public class CacheService implements InitializingBean {
@Override
public void afterPropertiesSet() {
this.loadCache(); // 缓存预热
}
}
可以看到上述代码功能,在实现了InitializingBean的afterPropertiesSet方法后就会在此时被调用,如果你有时间的话,你可以看下数据库连接池的初始化连接就是在这里进行实现的。
PostConstruct方法执行
如果你是一个开发人员,你一定见过这种代码:
@Component
public class LifecycleDemo {
@Autowired
private Dependency dep;
public LifecycleDemo() {
System.out.println("[实例化] dep=" + dep); // null
}
@PostConstruct
public void init() {
System.out.println("[初始化] dep=" + dep); // 已注入
}
}
这里的@PostContruct方法就是在这里实现的,我会用大白话来阐述一下Spring是怎么执行的这个方法的过程。
- 首先,Spring 扫描 Bean 类中所有带 @PostConstruct 注解的方法,然后进行方法验证,要求方法必须为 void 返回类型
同时必须无参数,同时不能是 static 方法。然后再进行初始化的时候在这里进行执行。 - 要注意的是这个时候相关的bean都已经完成了实例化,同时又完成了属性注入,这才是让他们能正确执行的基础。
自定义init方法
在现在的开发中,我们大部分用的可能是通过注解的开发,比如使用@Componet,@Service等来让Spring帮我管理类。但是对于之前的一些Bean是通过XML方式来进行加载和管理的,如下代码:
<bean id="legacyService" class="com.example.LegacyService"
init-method="traditionalInit"/>
BeanPostProcessor后置处理
接下来就是BeanPostProcessor后置处理,这个和前置处理很像,都是进行方法增强或者检查。
至此在本文中已经完全的介绍了整个Bean的实例化,和相关的实现细节,下面我们就能正常的使用这些初始化后的Bean。
4. 使用阶段
说明:因为使用阶段使我们打交道最多的一个阶段,因此,这里会省略很多的内容。可以看下其他的文章,比如:怎么注入,AOP怎么进行使用等。
作用域对使用的影响
因为本质上作用域还是属于使用阶段,受其他或者第三发影响因素较大,具有多样性,因此在这里简单提下:
5. 销毁阶段
当你能仔细并耐心的读到这里的时候,恭喜你,你已经进入到了Bean中的最后一个阶段,你已经伴随Bean走过了它的一生,最终它也即将步入了消亡的阶段。我们还是通过一个类比的例子来告诉你,Bean是怎么在完成他的使命后是怎么被消亡的,如果宣告它已经“退休”。
Bean的消亡就像人“退休”要办手续一样,Bean在"退休"时也需要完成一系列清理工作。就像我们一样,我们在什么情况下才能被宣告“退休”尼?
- 老板(容器)宣布公司(应用)要关门了。
- 网站要打烊了(Web应用关闭)。
- 提前写好的退休计划(@PreDestroy)。
碎碎念,现在工作也太难找了。
我们从"退休"的案例来进行类比,说明。第一步当公司停业公告(容器关闭信号),也就是就像公司要关门时会提前发通知一样,Spring容器关闭前也会发出信号:
public class Company {
public void announceShutdown() {
System.out.println("🏢 公司公告:本月末将停止运营");
// 相当于applicationContext.close()
}
}
可以看上述代码,applicationContext.close()这里就是我们实际的容器关闭的代码,当执行了这个代码就说明,要关闭Spring容器了。下面我们就开始,真正的办理"退休"手续开了,还是看下代码:
public class Employee {
@PreDestroy // 就像离职清单上的第一项
public void clearDesk() {
System.out.println("🧑💼 员工:整理个人物品、删除电脑隐私文件");
}
}
可以看代码我们在方法中是存在了一个@PreDestroy的注解,这就像每个员工离职时都要先完成的标准化流程,Spring保证所有Bean的@PreDestroy方法都会被执行。当我们整理好了我的个人物品,是不是就要进行手头的工作交接。看下面代码:
public class DepartmentManager implements DisposableBean {
@Override
public void destroy() {
System.out.println("👨💼 部门经理:交接客户资料和项目进度");
}
}
注意的是,这个方法是实现了一个接口DisposableBean,就像是管理层需要额外交接工作,实现DisposableBean的Bean就像这些需要特殊交接流程的经理。在这里我们的普通员工和管理层都已经完成了工作交接,准备离去,但是我们的CTO想发表一些告别演讲怎么办。看下面代码,可以实现这个功能:
public class CTO {
public void farewellSpeech() {
System.out.println("👨💻 技术总监:发表感人告别演讲");
}
}
@Bean(destroyMethod = "farewellSpeech")
public CTO chiefTechnicalOfficer() {
return new CTO();
}
当所有的普通员工,管理人员,CTO等都离开了公司,最后就是我们的高管才宣布离场:
public class CEO implements SmartLifecycle {
private boolean onDuty = true;
@Override
public void stop() { // phase值最小的最后执行
System.out.println("👔 CEO:检查所有门窗,最后锁门离开");
onDuty = false;
}
// ...其他必要方法实现
}
就像CEO要确保所有员工安全离开后才最后锁门,SmartLifecycle可以控制Bean的关闭顺序。请仔细观察上述场景,有些关键的方法一定要注意,分别是:
- @PreDestroy
- DisposableBean
- destroyMethod
这些方法,就是Bean在容器中实际上进行销毁的过程,值得注意的是,请注意他们的执行顺序,就像员工"退休"一样,先是普通员工,再是管理人员,再是高管CEO等, 如果离开的顺序发生错误,就会有很多的麻烦,比如: 在公司关闭前,CTO先"跑路了",这个后果不言而喻。同样类比到Bean的销毁顺序一样,如果没有正确顺序的销毁,可能会就给系统带来内存泄漏等问题。
请注意:上面提到的Bean的销毁,也就是普通员工的"退休"都是公司的正式员工,就是长时间待在公司的人群,也就是我们在代码中说的单例Bean。在实际场景中,公司可能还会存在一些临时工,类比到Spring中就是原型Bean(prototype),他们是不用参加整个公司集体退休仪式的,需要管理者(客户端代码)单独通知他们离职。
说到这相信你已经对Bean的销毁了然于胸了,下面就实际的开发中在展开说说Bean的销毁是怎么执行。或许你看完上述内容,可能会有疑问?
你说的这么多方法,我平时在代码中也没写这些方法呀,什么@PreDestroy,DisposableBean等,我一个业务功能执行完就拉倒,或者直接关闭应用就结束了,哪里有这么多操作。
这个问题的答案就是:Spring Bean销毁的自动化处理机制会帮你处理Bean的销毁。
我们简单的代码入口和对应策略的处理逻辑分别看下:
// 简化后的源码逻辑
public void close() {
// 1. 发布容器关闭事件
publishEvent(new ContextClosedEvent(this));
// 2. 执行所有单例Bean的销毁
getBeanFactory().destroySingletons();
}
当容器关闭时,核心处理逻辑在AbstractApplicationContext中,这个就是我们上面提到了,当调用了ApplicationContext.close()方法后,我先看下他的判断逻辑:
判断一个类是哪种销毁逻辑后,根据类型帮我们自动的进行处理:
@PreDestroy处理
public class InitDestroyAnnotationBeanPostProcessor
implements DestructionAwareBeanPostProcessor {
// 在Bean初始化时扫描注解
public void postProcessBeforeDestruction(Object bean, String beanName) {
// 通过反射找到所有@PreDestroy方法
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
metadata.invokeDestroyMethods(bean);
}
}
Spring通过InitDestroyAnnotationBeanPostProcessor实现。
DisposableBean自动调用
public void destroy() {
// 先执行@PreDestroy(如果有)
if (this.bean instanceof DisposableBean) {
((DisposableBean) this.bean).destroy();
}
// 再执行自定义destroy方法
invokeCustomDestroyMethod();
}
destroyMethod的智能推断
// 在BeanDefinition中推断
if (bean instanceof Closeable || bean instanceof AutoCloseable) {
// 自动识别close()/shutdown()方法
inferredDestroyMethod = "close";
}
上面就是分别对应的三种处理销毁的核心代码,只要进行了解即可,同时在需要进行注意的的是,一个Bean完全可以同时使用多种销毁方式,Spring会按照固定顺序执行它们。
至此我们的Bean的销毁已经阐述完毕了,同时我们的SpringBean的生命周期也完成了,最后再用一张完成的流程图来说明一下:
说明:本文中没有过多的介绍底层源码相关,更多的让你去理解其中的逻辑,文中用了大量的类比去尽量还原真实的Spring中的处理场景,尽量的去减少大家的理解负担。
如果感觉文章还不错,点点赞,再给个关注。