一文搞定Spring生命周期

前言

⚠️⚠️⚠️
本文超级长,文字密集恐惧症请避让

自打从事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 容器就像个智能幼儿园​​
  1. 园长:负责管理所有小朋友(Bean)的入园到毕业
  2. 花名册:记录每个小朋友的档案(配置信息)
  3. 保育员:专门照顾小朋友的日常生活(依赖注入、生命周期回调)

注:括号内是类比实际的Bean在Spring容器中的行为

我们看下小朋友在小朋友在幼儿园的一天(生命周期流程)


结合上述图片我们分别对图片中的每个节点进行说明,并类比到Bean实例中去。

  1. 入园登记(实例化)
  • 园长叫名字:“小明!”(根据配置创建Bean)
  • 新生领书包水杯(构造方法初始化)
  1. 分配座位(依赖注入)
  • 老师安排同桌:“小明和小红坐一起”(@Autowired注入)
  • 发放文具(设置属性值)
  1. 晨检(初始化)
  • 保健老师检查(@PostConstruct)
  • 系好鞋带(InitializingBean回调)
  • 确认健康状态(BeanPostProcessor处理)
  1. 上课活动(使用期)
  • 正常上课学习(处理业务逻辑)
  • 单例小朋友:一直用同一个(singleton)
  • 原型小朋友:每次活动换新人(prototype)
  1. 放学回家(销毁)
  • 老师检查书包(@PreDestroy)
  • 和家长交接(DisposableBean回调)
  • 挥手再见(资源释放

通过上述幼儿园类比的案例,我相信大家已经对Bean生命周期以及,Spring容器是如果管理Bean生命周期已经有了大致的了解。同时对其中的一些专业的类名和名词有了一点点的印象,如果在下文中出现了一样的名词,请不要惊讶。

标准生命周期阶段详解

相信大家在看完幼儿园的的案例后,是对Bean生命周期有一定的了解,那么我们还是用幼儿园类比一下什么是标准生命周期的阶段详解。在开始前我们思考一下“标准”这个词,什么是标准?他是不是就是给Bean的定制了一套规则,在什么阶段Bean就需要干什么事情,然后我们把这一套规则应用于每一个Bean实例中,让他们按照我们这一套规则来进行成长。而这样的一套规则就是标准。我们直接看以下例子:

  1. 入园报到(实例化阶段)
  • 就像新生入学登记​​:
  • 园长查名单:根据班级名单(配置)点名
  • 发学生证:new Student() 创建对象实例
  • 典型问题​​:双胞胎取名冲突(重复Bean定义报错)
  1. 分配座位(属性赋值阶段)
  • 老师安排教室座位​​:
  • 同桌分配:@Autowired private DeskMate deskMate;
  • 发课本:setTextbook(new MathBook())
  • 特殊照顾​​:残疾同学配拐杖(特殊依赖处理)
  1. 入学体检(初始化阶段)
  • 分三步检查​​:
  • 打疫苗:@PostConstruct public void init()
  • 体检表:InitializingBean.afterPropertiesSet()
  • 班主任评语:BeanPostProcessor.postProcessBeforeInitialization()
  1. 上课学习(使用期)
  • 正常教学活动​​:
  • 单例学生:整个幼儿园期间就这1个(默认)
  • 原型学生:每节课都克隆新同学(@Scope(“prototype”))
  • 课堂纪律​​:AOP代理像班长,管理方法调用
  1. 毕业离校(销毁阶段)
  • 放学前的收尾工作​​:
  • 收拾书包:@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是不是对我们的加载类和实例化有了很大的帮助?当然我们只是对这个类做了最简单的阐述,目的就是让我们大致了解他的作用是啥。

接下来我们知道了实例化的必要条件后再看下什么是实例化,他该怎么解释,以及它的作用啥?

  1. 什么是实例化?

实例化​​就是把Java类的Class定义变成真正的对象的过程,相当于用"设计图"造出"实物"。

  1. 通俗类比
  • 类 = 汽车设计图(包含所有参数和功能描述)
  • 实例 = 来的真实汽车(可以实际驾驶)
// 设计图
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 等注解的成员。

  • ​​解析依赖​​:

  1. 如果是其他 Bean(如 UserRepository),从容器中查找并注入。
  2. 如果是配置值(如 @Value(“${db.url}”)),从配置文件中读取。
  3. ​​反射赋值​​:通过反射直接修改对象的字段值(即使字段是 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中的处理场景,尽量的去减少大家的理解负担。

如果感觉文章还不错,点点赞,再给个关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值