Spring 源码分析 Lifecycle Bean

前言

前面的文章我们介绍了 Spring 容器的普通 Bean 。我们已经熟悉了单例 Bean 的初始化与生命周期。然而,在真实的复杂应用中,我们常常面临另一类需求:如何在容器启动完成后,自动开启某个服务(如消息监听、端口监听)?如何在容器关闭前,优雅地停止正在处理的请求(如内嵌的 Web 服务器)?

这就是 Spring 容器级生命周期接口------Lifecycle 的核心舞台。

本篇文章使用的 SpringBoot 版本是 3.4.1 ,对应 Spring 版本 6.2.1

SpringBoot & Spring 架构图示概览

这里我以 SpringBoot 源码入口为起点,画了一个相关的流程图,包含了 SpringBoot、Spring 事务、Spring AOP、Spring 事件、BeanFactoryPostProcessor、BeanPostProcessor 等所有 Spring 知识,以及相关模块之间的交互联系,后续也会持续更新此图(因为我自己还没有学完),我试了下作者侧这边更新后,分享的协作链接也会实时变更,希望对大家有帮助

SpringBoot & Spring 架构图 持续更新 对于即将需要面试的同学应该会比较有帮助!

Lifecycle

核心概念

LifecycleSpring 框架提供的一个通用接口,用于定义组件的启动和停止生命周期控制。它的定义极其简洁:

java 复制代码
public interface Lifecycle {
    void start();
    void stop();
    boolean isRunning();
}

任何由 Spring 管理的 Bean 都可以实现这个接口。当 ApplicationContext 自身接收到启动或停止信号时(例如在运行时停止/重启场景),Spring 容器将会找出所有实现了 Lifecycle 接口的 Bean,并一一决定是否调用它们对应的方法。

与普通 Bean 生命周期的区别

参考之前的单例 Bean 生命周期流程图

  • Bean 初始化阶段 :发生在 AbstractApplicationContext.finishBeanFactoryInitialization() 中。此时 Spring 负责装配 Bean、注入依赖、执行 afterPropertiesSet() 等操作。这是 Bean "准备好" 的阶段。
  • Lifecycle 启动阶段 :发生在 AbstractApplicationContext.finishRefresh() 中。此时容器已经刷新完毕,所有单例 Bean 都已就绪。Spring 会主动调用那些"希望自动启动"的组件SmartLifecycle.start() 方法。这是组件 "开始工作" 的阶段。

我们可以这么理解:普通单例 Bean 生命周期解决的是"如何造好一辆车"的问题,而 Lifecycle 解决的是"何时发动引擎"的问题。

生命周期方法触发时机

Spring 容器本身并不直接遍历所有 Bean 来调用 start()。这个繁琐的工作被委托给了一个专门的组件:LifecycleProcessor

LifecycleProcessor 本身也是一个 Lifecycle,它增加了两个特定于上下文的方法

java 复制代码
public interface LifecycleProcessor extends Lifecycle {
    void onRefresh(); // 容器刷新时触发
    void onClose();   // 容器关闭时触发
}

它的默认唯一实现类是 DefaultLifecycleProcessor。当 AbstractApplicationContext 执行 finishRefresh() 方法时,会调用 lifecycleProcessor.onRefresh()

java 复制代码
//org.springframework.context.support.AbstractApplicationContext#finishRefresh
protected void finishRefresh() {
    //...

    // 通过 LifecycleProcessor 调用 Lifecycle Bean
    getLifecycleProcessor().onRefresh();

    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));
}

LifecycleProcessor

它是执行实现了 Lifecycle 接口的 Bean 的生命周期方法的处理器,默认实现类是 DefaultLifecycleProcessor,我们查看 DefaultLifecycleProcessor.onRefresh() 有一行关键代码调用了 DefaultLifecycleProcessor#startBeans()

java 复制代码
//org.springframework.context.support.DefaultLifecycleProcessor#startBeans 启动 Lifecycle Bean
private void startBeans(boolean autoStartupOnly) {
    //从 Spring 容器获取所有实现了 Lifecycle 的 Bean
    Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
    //有顺序的集合,按阶段分组
    Map<Integer, LifecycleGroup> phases = new TreeMap<>();

    lifecycleBeans.forEach((beanName, bean) -> {
       //如果是自启动组件,按顺序放进 phases 分组 Lifecycle Bean
       if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
          int startupPhase = getPhase(bean);
          phases.computeIfAbsent(startupPhase,phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, autoStartupOnly)).add(beanName, bean);
       }
    });

    if (!phases.isEmpty()) {
       //遍历启动 Lifecycle Bean
       phases.values().forEach(LifecycleGroup::start);
    }
}

我们再看 LifecycleGroup.start() 源码,其内部真正工作的方法是 DefaultLifecycleProcessor#doStart

java 复制代码
//org.springframework.context.support.DefaultLifecycleProcessor#doStart
//递归启动 Lifecycle Bean
private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
    Lifecycle bean = lifecycleBeans.remove(beanName);
    //如果是实现了 Lifecycle 的 Bean
    if (bean != null && bean != this) {
       //先获取依赖的 Bean
       String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
       for (String dependency : dependenciesForBean) {
          //递归,先启动依赖的 Bean
          doStart(lifecycleBeans, dependency, autoStartupOnly);
       }
       if (!bean.isRunning() && (!autoStartupOnly || toBeStarted(beanName, bean))) {
          try {
             //调用 Lifecycle Bean 的 start()
             bean.start();
          }
          //...
       }
    }
}

SmartLifecycle

SmartLifecycleLifecycle 的子接口,在此基础上做了一些扩展

java 复制代码
public interface SmartLifecycle extends Lifecycle, Phased {
    int DEFAULT_PHASE = Integer.MAX_VALUE;

    /**
     * 是否自启动,如果返回 true ,Spring 容器刷新完成后会调用该组件的 start() 方法
     */
    default boolean isAutoStartup() {
       return true;
    }

    /**
     * 异步停止
     */
    default void stop(Runnable callback) {
       stop();
       callback.run();
    }

    /**
     * 运行阶段,控制 Lifecycle Bean 的执行顺序,越大 start() 调用越靠后、stop() 调用越靠前
     */
    @Override
    default int getPhase() {
       return DEFAULT_PHASE;
    }

}

可以看到主要提供了异步停止方法,以及新增一个继承接口 Phased ,重写 getPhase() 来控制 Lifecycle Bean 的启动顺序。

自动启动

DefaultLifecycleProcessor#onRefresh 源码我们可以看到

java 复制代码
@Override
public void onRefresh() {
    //...
    try {
       startBeans(true);
    }
}

startBeans() 方法的入参仅自动启动传入的是 false,那么在这个方法源码中

java 复制代码
//org.springframework.context.support.DefaultLifecycleProcessor#startBeans 启动 Lifecycle Bean
private void startBeans(boolean autoStartupOnly) {
   //从 Spring 容器获取所有实现了 Lifecycle 的 Bean
   Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
   //有顺序的集合,按阶段分组
   Map<Integer, LifecycleGroup> phases = new TreeMap<>();

   lifecycleBeans.forEach((beanName, bean) -> {
      //如果是自启动组件,按顺序放进 phases 分组 Lifecycle Bean
      if (!autoStartupOnly || isAutoStartupCandidate(beanName, bean)) {
         int startupPhase = getPhase(bean);
         phases.computeIfAbsent(startupPhase,phase -> new LifecycleGroup(phase, determineTimeout(phase), lifecycleBeans, autoStartupOnly)).add(beanName, bean);
      }
   });

   if (!phases.isEmpty()) {
      //遍历启动 Lifecycle Bean
      phases.values().forEach(LifecycleGroup::start);
   }
}

按阶段分组是否自启动的判断就会被 isAutoStartupCandidate() 决定,我们看它的实现

java 复制代码
private boolean isAutoStartupCandidate(String beanName, Lifecycle bean) {
    Set<String> stoppedBeans = this.stoppedBeans;
    return (stoppedBeans != null ? stoppedBeans.contains(beanName) :
          (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup()));
}

这里是否是自启动组件的判断条件就是:这个 BeanSmartLifecycle 类型,并且 isAutoStartup() 返回 true

也就是说如果我们的组件仅实现 Lifecycle 接口,我们需要手动调用它的 start()stop() 生命周期方法。而绝大多数场景下,我们是需要在 Spring 容器刷新后、关闭前自动执行组件的生命周期方法,所以我们通常会实现 SmartLifecycle

总结

实现 SmartLifecycle 接口的 Bean 会在 Spring 容器的生命周期过程中自动被调用,而实现 Lifecycle 不会。

下面我们以实际场景为例来感受 SmartLifecycle 的作用。

SpringBoot 内嵌 Web 服务器

理解了理论,我们来看一个最具代表性的实际案例------Spring Boot 内嵌 Web 服务器(如 Tomcat)的启动

在传统的 Java Web 应用中,我们需要将应用打包成 WAR 包,部署到外部 Tomcat 容器中,然后启动外部容器。而在 SpringBoot 中,只需要一个 main 方法,Web 服务器就自动启动了。这背后的魔法,就与 SmartLifecycle 密切相关。

谁创建了 Tomcat

SpringBoot 扩展的 ApplicationContext 实现类 AnnotationConfigServletWebServerApplicationContext 在容器刷新时会调用父类 AbstractApplicationContext.refresh(),这个过程中会调用子类实现的 ServletWebServerApplicationContext#onRefresh 创建服务器

java 复制代码
//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
       //创建 Web 服务器,Tomcat/Jetty 等
       createWebServer();
    }
    catch (Throwable ex) {
       throw new ApplicationContextException("Unable to start web server", ex);
    }
}

谁启动了 Tomcat

在这个上下文的 onRefresh() 方法中,它会调用 createWebServer() 来创建 Web 服务器(实例化 Tomcat 对象、添加连接器等)。但此时,Tomcat 虽然被创建了,却 还没有开始监听端口。也就是说,在这里,应用还不能对客户端请求进行处理

WebServerStartStopLifecycle

真正启动 Tomcat 的时机,就在 finishRefresh() 阶段。SpringBoot 定义了一个名为 WebServerStartStopLifecycle 的类。这个类实现了 SmartLifecycle。重写了 start()、stop() 方法

java 复制代码
class WebServerStartStopLifecycle implements SmartLifecycle {

    private final ServletWebServerApplicationContext applicationContext;

    private final WebServer webServer;

    private volatile boolean running;

    @Override
    public void start() {
       //web 服务器开始对外提供处理
       this.webServer.start();
       this.running = true;
       this.applicationContext
          .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
    }
    
    @Override
    public void stop() {
        this.running = false;
        this.webServer.stop();
    }
    //...

}

前面我们介绍了,容器刷新完成后会遍历调用 Lifecycle Bean,当调用WebServerStartStopLifecycle.start() 方法结束后,Tomcat 服务就可以对外提供服务,处理客户端请求

执行流程

  1. Bean 创建SpringBoot 在自动配置阶段创建了 TomcatServletWebServerFactory,进而创建了 TomcatWebServer 实例。此时 Tomcat 对象存在,但未启动端口监听。
  2. 容器刷新完毕AbstractApplicationContext 执行 finishRefresh()
  3. 调用 LifecycleProcessorDefaultLifecycleProcessor 开始寻找所有自动启动的 SmartLifecycle
  4. 找到 WebServerStartStopLifecycle :根据 getPhase() 的返回值,它可能在所有业务组件都准备好之后才被调用。
  5. 执行 start()webServer.start() 方法被执行,Tomcat 正式打开端口,开始接受 HTTP 请求。

体现的好处

通过这个机制,SpringBoot 实现了以下几点好处:

  1. 关注点分离Web 服务器的创建 (实例化对象、配置连接器)和启动 (监听端口)被清晰地分开了。创建在 AbstractApplicationContext#onRefresh 早期,启动在 AbstractApplicationContext#finishRefresh 晚期。
  2. 保证组件就绪 :确保所有的 Spring 容器 Bean 都初始化完毕、所有的 BeanPostProcessor 都执行完成之后,才对外开放服务(开启端口)。这避免了请求进来时 Bean 还没准备好的尴尬。
  3. 优雅停机的基础 :当应用关闭时,Spring 会发送停止信号,WebServerStartStopLifecyclestop() 方法被调用,从而触发 Web 服务器的优雅停机(等待现有请求处理完成,拒绝新请求)。

RabbitMQ 消息监听的实现

让我们设想一个场景,当使用 Spring 框架整合某个消息队列产品时,到底在应用启动的什么时机才能让此应用来消费消息呢?它也是应用了 SpringLifecycle 接口。这里我们以 RabbitMQ 为例,引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

然后编写监听代码

java 复制代码
@RabbitListener(queues = "xx")
public void receiveMessage(String message) {
    System.out.println(message);
}

这样有一个实现了 SmartLifecycle 接口的类就会在 Spring 容器刷新完成后调用 start() 方法,他就是 RabbitListenerEndpointRegistry

java 复制代码
@Override
public void start() {
    for (MessageListenerContainer listenerContainer : getListenerContainers()) {
       startIfNecessary(listenerContainer);
    }
}

这里会根据配置获取到所有消息容器,包括 SIMPLE、DIRECT、STREAM 等,这里会调用具体实现类例如 SimpleMessageListenerContainerstart() 方法,其内部调用真正的消息消费方法 doStart(),这里只介绍实现思想,不过多介绍 spring-rabbitmq 的代码细节

结语

通过这篇文章我们知道,只要我们有一些需要在 Spring 容器刷新完毕后、关闭前需要执行的系统级别的组件,都可以实现 SmartLifecycle 接口。

如果这篇文章对你有帮助,记得点赞加关注!你的支持就是我继续创作的动力!

相关推荐
渣瓦攻城狮2 小时前
互联网大厂Java面试实战:核心技术与场景分析
java·大数据·redis·spring·微服务·面试·技术分享
倚肆2 小时前
WebSocket连接教程示例(Spring Boot + STOMP + SockJS + Vue)
vue.js·spring boot·websocket
程序猿零零漆2 小时前
【Spring Boot开发实战手册】掌握Springboot开发技巧和窍门(六)创建菜单和游戏界面(下)
java·spring boot·游戏
GEM的左耳返2 小时前
Java面试深度剖析:从JVM到云原生的技术演进
jvm·spring boot·云原生·中间件·java面试·分布式架构·ai技术
indexsunny3 小时前
互联网大厂Java面试实录:Spring Boot与微服务在电商场景中的应用
java·jvm·spring boot·微服务·面试·mybatis·电商
人道领域3 小时前
SpringBoot整合Junit与Mybatis实战
java·spring boot·后端
Coder_Boy_3 小时前
Java高级_资深_架构岗 核心知识点全解析(通俗透彻+理论+实践+最佳实践)
java·spring boot·分布式·面试·架构
笨蛋不要掉眼泪3 小时前
Sentinel 热点参数限流实战:精准控制秒杀接口的流量洪峰
java·前端·分布式·spring·sentinel
源码获取_wx:Fegn08954 小时前
计算机毕业设计|基于springboot + vue家政服务平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计