Spring Boot 遵循约定大于配置的核心思想,让我们可以轻松创建开箱即用的生产级别 Spring 应用。作为一个被广泛使用的框架,它在生命周期的各个阶段都提供了可扩展点来满足开发者的各种使用场景,本文就以它的生命周期为序,梳理并整理它潜在的各种特性。

本文章所述实现基于 Spring Boot 2.6.4、Spring Cloud 3.1.0。

Spring Boot 应用通过 Java 的 main 方法作为入口启动整个程序,内部我们调用 SpringApplicationSpringApplicationBuilder 类的静态 run 方法启动 Spring 应用。

在启动前,我们也可以使用它的其它静态方法修改配置,例如设置启动横幅、设置环境变量、设置监听器、设置指标收集器、设置关闭处理器、设置是否延迟初始化、是否允许 Bean 覆盖定义、是否允许循环引用等,这里我们单独说一下其中几个:

首先是指标收集器 ApplicationStartup,该类主要负责在应用程序启动期间标记基础组件的不同阶段,并收集其有关执行上下文或处理时间等数据。默认实现不做任何操作,我们也可以使用基于 JFR - Java Flight Recorder 飞行记录器的实现 FlightRecorderApplicationStartup 来记录各种组件的启动等信息,便于分析和监测应用。

如果我们需要在 JVM 关闭前做一些清理等操作,可以通过调用 getShutdownHandlers 后向其中添加或移除处理器。

run 方法内部,下方列出的核心步骤需要了解:

image-20220424184508918

  • 创建引导上下文对象 createBootstrapContext
  • 获取启动监听器列表 getRunListeners
  • 调用监听器 starting 正在启动方法
  • 初始化环境 prepareEnvironment
  • 创建应用上下文 createApplicationContext
  • 初始化上下文 prepareContext
  • 刷新上下文 refreshContext
  • 调用监听器 started 已启动方法
  • 调用 callRunners
  • 调用监听器 ready 方法
  • 期间如果出错,调用监听器 failed 方法

这里的核心流程主要是:构建引导上下文构建环境构建应用上下文刷新应用上下文,并在期间发布不同阶段事件。

这里就必须得先介绍下启动监听器、引导上下文和应用上下文。

启动监听器

启动监听器 SpringApplicationRunListener 在启动的不同阶段被调用,并将该阶段对应的上下文参数传递过去供实现者来执行监听操作。该监听器被 Spring 使用 SpringFactoriesLoader SPI 工厂加载器从 “META-INF/spring.factories" 文件中获取定义的类型并加载,通过这种服务提供商接口 SPI 机制,很好的将接口定义和实现解耦,事件机制被大量用到 Spring 内部。

Spring 在该文件中为我们提供了一个默认的实现 EventPublishingRunListener,该监听器在不同阶段方法被调用时广播相应的事件给 ApplicationListener 实现。这里的实现一部分也是通过 SPI 机制在构建 SpringApplication 时加载进来的,我们也可以在启动前配置 SpringApplication 的该监听器。

事件中暴露给监听器实现的对象都是可配置的,例如 可配置的 BootstrapContext ConfigurableBootstrapContext,可配置的 Environment ConfigurableEnvironment,可配置的 ApplicationContext ConfigurableApplicationContext,所以它允许监听器实现安全的修改和配置这些对象。

ApplicationListener 作为 Spring 事件机制的核心接口,基于标准 java.util.EventListener 接口,使用观察者设计模式,被大量应用在程序中,它不仅可以监听 Spring Boot run 阶段的 SpringApplication 事件,也可以监听应用上下文 ApplicationContext 各个阶段的事件监听,用户也可以使用它自定义自己的事件和广播器等。

更多关于 ApplicationListener 事件的使用请参考:Standard and Custom Events

这种事件驱动的架构及思想在很多大型应用中被广泛使用,例如 Hibernate、Kafka 等。

这些 SpringApplication 不同阶段事件的监听器在 Spring 内部担任了绝大多数的工作,我们将对这些阶段及对应阶段事件监听器做一个简单介绍:

starting

在 run 方法首次启动时立即被调用。可以用于非常早期的初始化。暴露 ConfigurableBootstrapContext 实例给监听器。

默认实现 EventPublishingRunListener 发布 ApplicationStartingEvent 事件,主要的监听器有:

  • RestartApplicationListener

    监听启动事件,并从系统环境变量决定是否允许重启并配置 Restarter

    Restarter 允许使用更新后的 classpath 重新启动正在运行的应用程序,Spring Boot Actuator 的 /restart Endpoint 请求处理器就使用的该类,Spring Boot DevTools 监听文件变更并自动重启也使用了该类。

  • LoggingApplicationListener

    监听启动事件,并从 SpringApplication 类加载器中确定日志系统实现,Spring 使用 LoggingSystem 接口表示日志系统的通用抽象,并对常见的日志系统提供了封装。在日志系统未初始化完之前,日志会被存储在 DeferredLog 实例中。

environmentPrepared

在环境准备好后,ApplicationContext 还未创建之前被调用。暴露 ConfigurableBootstrapContext 实例和准备好的 ConfigurableEnvironment 给监听器。

默认实现发布 ApplicationEnvironmentPreparedEvent 事件,这个阶段的监听器主要会根据环境中的环境变量来做操作,也允许对环境做一些自定义的操作:

  • BackgroundPreinitializer

    该监听器在监听到环境准备好事件后会提前预初始化耗时较长的任务线程,如果在初始化过程中监听到应用的就绪事件 ApplicationReadyEventApplicationFailedEvent 失败事件,由于默认实现 EventPublishingRunListener 中的广播器未使用线程池,则相当于同步调用监听器会阻塞主线程直到这些耗时任务线程初始化完。

    这里并不是真正将这些服务加载到应用中,只是进行代码预热,提前加载类,所以叫预初始化。

    一旦类加载完成,所有重要的类(在进程启动时使用)都会被推送到 JVM 缓存(本机代码)中,这使得它们在运行时可以更快地访问。

    这些耗时任务包括:

    • ConversionServiceInitializer

      该初始化器预热默认的格式转换服务,该格式转换服务作为注册中心,用于注册配置适用于大多数应用程序的默认转换器 Converter 和默认格式化器 Formatter

      添加适用于大多数环境的转换器:例如不同日期类型之间的转换器,或不同对象类型之间的转换器,具体查看:DefaultConversionService.addDefaultConverters

      添加适用于大多数环境的格式化程序:包括数字格式化程序、JSR-354 货币和货币格式化程序、JSR-310 日期时间和/或 Joda-Time 格式化程序,具体取决于类路径中相应 API 的存在,具体查看:DefaultFormattingConversionService.addDefaultFormatters

    • ValidationInitializer

      该初始化器使用 javax 的 Validation 来引导构建默认的 Bean Validator 验证器。

    • MessageConverterInitializer

      该初始化器预热增强的 form 表单消息转换器 AllEncompassingFormHttpMessageConverter,是我们可以处理基于 JSON 和 XML 格式消息的表单。

    • JacksonInitializer

      该初始化器预热 Jackson 的 ObjectMapper 构建过程及其类加载。

    • CharsetInitializer

      该初始化器预热 Charset 类。

  • AnsiOutputApplicationListener

    通过环境变量确定是否启用 ANSI 转义序列输出,启用后可以输出基于 ANSI 的彩色日志。

  • LoggingApplicationListener

    日志监听器在该阶段获取相关环境变量来设置日志系统参数,初始化日志系统,设置日志级别,注册日志系统关闭钩子等。

  • SpringBootServletInitializer

    基于 Web 的 Environment 会在这时将 Servlet 的 ServletContext 设置到环境中。

  • EnvironmentPostProcessorApplicationListener

    该监听器会调用所有通过 SPI 机制加载的 EnvironmentPostProcessor 环境后置处理器的 postProcessEnvironment 方法,该后置处理器允许在刷新应用程序上下文之前自定义修改应用程序的 Environment,典型的该类型的后置处理器实现包括:

    • ConfigDataEnvironmentPostProcessor

      加载并应用 ConfigData 到包裹的 ConfigDataEnvironment 环境中,ConfigData 使用其 ConfigDataResource 对应的解析器从配置的 location 地址获取配置数据并最终将配置属性加载到环境中。

      location 从 spring.config.location 默认配置路径,或者 spring.config.additional-locationspring.config.import 额外的配置资源地址下解析 spring.config.name 文件名的 properties 和 yml 后缀的配置。如果有激活的 profile,同时加载 ${spring.config.name}-profile 的配置文件。

      spring.config.name 默认为 “application” 文件名; spring.config.location 默认为: 内部: optional:classpath:/;optional:classpath:/config/ 外部:optional:file:./;optional:file:./config/;optional:file:./config/*/

      location 前面可省略的 optional: 代表可选,接下来的 location 由解析器通过前缀来确定是否支持解析。

      ConfigDataResource 主要的实现类型有:

      • 基于 Spring Resource 资源的标准 StandardConfigDataResource,其解析器解析任意格式的 location。

        其解析器通过分号拆分 location,然后使用 DefaultResourceLoader 加载 URL 样式的资源:例如支持模式匹配的相对或绝对路径的 file:location 资源、classpath:location 资源,或者基于 java.net.URL 的 http、https、ftp 等协议 URL 资源。

      • 基于文件夹目录的 ConfigTreeConfigDataResource,其解析器支持 configtree: 前缀的 location。

        支持基于模式匹配的文件夹目录。

      • 基于外部服务的 ConfigServerConfigDataResource,其解析器支持 configserver: 前缀的 location。

        该实现从 spring.cloud.config 服务读取配置资源。

    • RandomValuePropertySourceEnvironmentPostProcessor

      将随机数属性来源 RandomValuePropertySource 添加到 Environment 的属性来源集合中,以便支持随机数 random. 前缀格式的配置属性。例如在微服务中我们经常会通过如下形式定义服务的 instanceId: uaa:${spring.application.instance-id:${random.value}}

    • SystemEnvironmentPropertySourceEnvironmentPostProcessor

      包装系统属性源,以便补偿 Bash 和其他 shell 中不允许包含句点字符和 / 或连字符的变量的约束,还允许属性名称的大写变体,以便更贴合 shell 使用。

      例如,调用 getProperty(“foo.bar”) 将尝试查找原始属性或任何“等效”属性的值,返回第一个找到的值:

      • foo.bar - 原始名称
      • foo_bar - 带有下划线和句点(如果有的话)
      • FOO.BAR - 原始,大写
      • FOO_BAR - 带下划线和大写
    • SpringApplicationJsonEnvironmentPostProcessor

      spring.application.json 或等效的 SPRING_APPLICATION_JSON 解析 JSON 并映射其属性源到 Environment。新添加的属性优先级高于系统属性。

    • Spring Cloud DecryptEnvironmentPostProcessor

      从环境中解密 Spring Cloud Config 的配置属性并以高优先级插入它们,以便覆盖它们加密的值。

  • Spring Cloud BootstrapApplicationListener

    该监听器具有较高的优先级,通过在单独的 Bootstrap 引导上下文中委托 ApplicationContextInitializer bean 来准备 SpringApplication(例如填充其环境)。

    Bootstrap Context 使用 spring.factories 中定义的 BootstrapConfiguration SPI 来构建,并使用 “bootstrap.properties” (或 yml) 来初始化。在得到引导上下文后,获取引导上下文 Boostrap Context 和 SpringApplicationn 中的 ApplicationContextInitializer 应用上下文初始化器并委托它在刷新上下文之前初始化 Application Context。

    BootstrapConfiguration 引导上下文配置类的几个主要供应商包括:

    • PropertyPlaceholderAutoConfiguration

      该自动配置类具有较高的优先级,提供缺省 PropertySourcesPlaceholderConfigurer Bean,该 Bean 可以使我们解析引导上下文和应用上下文 Environment 环境中 PropertySources 定义的属性值和 @Value 注释中的 ${…} 占位符。

    • PropertySourceBootstrapConfiguration

      该配置类实现了 ApplicationContextInitializer,在初始化的时候使用不同的 PropertySourceLocator 从外部或其它地方定位配置源并构建环境和应用上下文,像 ConsulNacosSpring Cloud vault 等远程配置中间件就是利用的该扩展点。

    • EncryptionBootstrapConfiguration

      该自动配置类为我们提供缺省时加密、解密需要的各种类。

    • ConfigurationPropertiesRebinderAutoConfiguration

      该自动配置类为我们提供缺省时的 ConfigurationPropertiesRebinder Bean,该 Bean 监听 EnvironmentChangeEvent 事件并重新绑定环境中的 @ConfigurationProperties 配置类,具有 @RefreshScope 注解的 Bean 可以在运行时重新绑定。

    • ConfigServerBootstrapConfiguration

      引导配置以从(可能是远程的) EnvironmentRepository 获取外部配置。

      默认关闭,因为它会降低启动速度,如果我们需要使用远程或第三方仓库,则可以使用 spring.cloud.config.server.bootstrap=true 启用。在 Spring Cloud Config 的 Server 端自动配置类会自动启用该配置,以便我们从 git、svn 等 scm 或者 redis、s3、jdbc、spring vault 等仓库存储和获取配置资源。

contextPrepared

在 ApplicationContext 已经被创建和准备好后,但在源被加载之前调用。暴漏 ConfigurableApplicationContext 给监听器。

该监听器在 prepareContext 方法中被调用,准备应用上下文时先设置之前准备好的 Environment 环境,接着设置资源加载器 ResourceLoader、类型转换服务 ConversionService,然后调用之前准备好的 ApplicationContextInitializer 来初始化应用上下文。之后调用监听器的 contextPrepared 方法。

监听器默认实现发布 ApplicationContextInitializedEvent 事件,监听该事件的内置监听器较少。这时候应用上下文已准备完毕,引导上下文将被关闭。

contextLoaded

在加载完应用程序上下文 ApplicationContext 后,但在刷新之前调用。暴漏完全加载好的 ConfigurableApplicationContext 给监听器。

默认实现会在该阶段为需要应用上下文的(实现 ApplicationContextAware 接口)所有监听器设置应用上下文,并广播 ApplicationPreparedEvent 事件给监听器。

该事件的监听器主要有:

  • RestartEndpoint

    提供 /restart 接口,用于重启应用。由于该事件暴漏的应用上下文为完全加载的上下文,重启时使用的上下文便是从该事件获取的。

  • LoggingApplicationListener

    日志系统在前几个阶段已根据配置初始化并设置后优先级,在该阶段会将相关的实体注册为 Spring 的 Bean。

  • ApplicationPidFileWriter

    在监听到 ApplicationPreparedEvent 事件时将程序 PID 保存到文件中。文件名可以在运行时使用名为 “PIDFILE” 或 “pidfile” 的系统属性或 spring.pid.file 环境变量属性在 Spring Environment 环境来覆盖。该监听器默认未启用,可以将它加到 SpringApplication 来使用。

started

上下文已刷新,应用程序已启动,但尚未调用 CommandLineRunnersApplicationRunners 。暴漏加载好的 ConfigurableApplicationContext 和程序启动耗时。

默认实现发布 ApplicationStartedEvent 事件,并通过 AvailabilityChangeEvent 事件发布 LivenessState.CORRECT 表明程序内部状态正确并正在运行的可用性状态。监听该事件的内置监听器较少。

ready

在 run 方法完成之前立即调用,此时应用程序上下文已刷新并且所有 CommandLineRunnersApplicationRunners 已被调用。暴漏加载好的 ConfigurableApplicationContext 和程序准备就绪耗时。

默认实现发布 ApplicationReadyEvent 事件,尽可能晚发布事件表明应用程序已准备好为请求提供服务。但要注意修改其内部状态,因为此时所有初始化步骤都已完成。

并通过 AvailabilityChangeEvent 事件发布 ReadinessState.ACCEPTING_TRAFFIC 已准备就绪事件表明应用程序处于 live 状态并愿意接受流量。

监听该事件的内置监听器主要有:

  • SpringApplicationAdminMXBeanRegistrar

    SpringApplicationAdminMXBean 实现注册到 MBeanServer 平台,其 ready 状态便是根据该事件来判断。

  • Spring Cloud RefreshEventListener

    收到 RefreshEvent 事件时调用 ContextRefresher.refresh。仅在收到 ApplicationReadyEvent 后才响应 RefreshEvent ,因为 RefreshEvents 可能在应用程序生命周期早期也被监听到。

    我们可以通过发布该事件来刷新上下文,例如:

    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void fireRefreshEvent() {
      eventPublisher.publishEvent(new RefreshEvent(this, "RefreshEvent", "Refreshing scope");
    }
    

    或者调用 /refresh 接口;

    刷新上下文时会先对比环境中的属性源变更的键,并通过 EnvironmentChangeEvent 事件将变更的键发布出去。然后刷新 RefreshScope 中的所有 Bean 。

failed

当运行应用程序发生故障时调用。暴漏应用上下文 ConfigurableApplicationContext 和异常。

监听该事件的内置监听器主要有:

  • ConditionEvaluationReportLoggingListener

    将记录自动配置 condition 条件评估详细信息的 ConditionEvaluationReport 写入日志 。报告的日志记录为 DEBUG 级别。在监听到该故障事件时报告会触发信息输出,建议用户在启用调试的情况下再次运行以显示报告。

  • LoggingApplicationListener

    日志监听器会在监听到故障事件时执行日志系统 LoggingSystemcleanUp 方法。

  • BootstrapApplicationListener

    如果在构建时 SpringApplication 发生故障,则关闭该引导上下文。

引导上下文 BootstrapContext

引导上下文在应用上下文准备好之前为应用提供上下文,在监听器的 startingenvironmentPrepared 事件调用中暴漏给对应事件的监听器提供上下文支持,例如构建日志系统、加载配置文件构建环境等。

在 Spring Cloud 环境下,Bootstrap Context 也负责加载外部配置源,加密、解密配置,占位符变量解析等,并获取加载到的 ApplicationContextInitializer 以用于初始化应用上下文,在之前的 BootstrapApplicationListener 已经详细介绍过。

在应用上下文准备好后,发布完 contextPrepared 事件,引导上下文将被关闭,它的使命就已经完成,接下来都是通过应用上下文提供服务。

应用上下文 ApplicationContext

ApplicationContext 是 Spring IOC 的核心容器,它是 BeanFactory 的超集。BeanFactory 是 Spring 的核心接口。

ApplicationContext 在应用程序运行时是只读的,如果实现支持,则可以重新加载。

ApplicationContext 提供:

  • 用于访问应用程序组件的 Bean 工厂方法。继承自 ListableBeanFactory
  • 以通用方式加载文件资源的能力。继承自 org.springframework.core.io.ResourceLoader 接口。
  • 向注册的侦听器发布事件的能力。继承自 ApplicationEventPublisher 接口。
  • 处理消息的能力,支持国际化。继承自 MessageSource 接口。
  • 从父上下文继承的能力。后代上下文中的定义将始终具有优先权。这意味着,例如,整个 Web 应用程序可以使用单个父上下文,而每个 servlet 都有自己的子上下文,该子上下文独立于任何其他 servlet

除了标准的 org.springframework.beans.factory.BeanFactory 生命周期功能之外,ApplicationContext 的实现检测和调用实现了ApplicationContextAware 以及 ResourceLoaderAwareApplicationEventPublisherAwareMessageSourceAware感知能力的接口,并将这些对应能力的实例传递给 Bean。

从上面 SpringApplication 的启动流程可以看到,在准备好应用上下文后,最重要的一个操作就是刷新上下文 refreshContext,该操作就是直接调用的应用上下文的刷新操作 refresh

刷新操作加载或刷新持久化表示的配置,这些配置可能来自基于 Java 的配置、XML 文件、属性文件、关系数据库模式或其他格式。并实例化配置中定义的 Bean,它的核心流程如下:

How exactly does the Spring BeanPostProcessor work? - Stack Overflow

refresh 方法的默认实现是通过抽象类提供的模版方法来表示,它描述了 refresh 中的核心步骤。

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // 准备上下文用于刷新
      prepareRefresh();

      // 告诉子类去刷新内部的 BeanFactory 工厂类
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 准备 bean factory 工厂类以在此上下文中使用
      prepareBeanFactory(beanFactory);

      try {
         // 允许在上下文子类中对 bean 工厂进行后处理
         postProcessBeanFactory(beanFactory);

         // 调用在上下文中注册为 bean 的工厂处理器
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册 bean 处理器以拦截 bean 创建
         registerBeanPostProcessors(beanFactory);
         beanPostProcess.end();

         // 初始化该上下文的 message source
         initMessageSource();

         // 初始化该上下文的事件广播器
         initApplicationEventMulticaster();

         // 初始化特定上下文子类中的其他特殊 bean
         onRefresh();

         // 检查监听器 bean 并注册它们
         registerListeners();

         // 实例化所有剩余的(非惰性初始化)单例
         finishBeanFactoryInitialization(beanFactory);

         // 最后一步:发布相应的事件
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // 销毁已经创建的单例以避免悬空资源
         destroyBeans();

         // 重置 “active” 标记
         cancelRefresh(ex);

         // 将异常传播给调用者
         throw ex;
      }

      finally {
         // 重置 Spring 核心中的常见自省缓存,因为我们可能不再需要单例 bean 的元数据了......
         resetCommonCaches();
         contextRefresh.end();
      }
   }
}

同样的,这里面有几个核心的阶段需要单独了解一下:

obtainFreshBeanFactory

由于应用上下文 ApplicationContext 是 BeanFactory 的超集,它通过组合的模式来扩展 BeanFactory 的功能,所以内部也维护了 BeanFactory 实例,在该方法调用时要求实现类该去刷新内部的 BeanFactory 来获取一个新的工厂类,这通常是一个 DefaultListableBeanFactory 类实例。

如果实现是支持刷新的 AbstractRefreshableApplicationContext 抽象基类的实现,则在多次调用 refresh() 时 ,每次都创建一个新的内部 bean 工厂实例。在每次刷新时,实现类会通过 loadBeanDefinitions 方法将 bean 定义加载到给定的 DefaultListableBeanFactory ,这通常是委托给一个或多个特定的 bean 定义读取器。

或者是一个包含单个 DefaultListableBeanFactory 工厂实例的通用 ApplicationContext 实现。由于 DefaultListableBeanFactory 实现了 BeanDefinitionRegistry 接口所以我们可以对其应用任何 bean 定义读取器来注册各种 bean 定义,然后在调用 refresh() 时使用应用程序上下文语义初始化这些 bean。与为每次刷新创建新的内部 BeanFactory 实例的其他 ApplicationContext 实现相比,此上下文的内部 BeanFactory 从一开始就可用,以便能够在其上注册 bean 定义。 refresh() 只能被调用一次。

prepareBeanFactory

传递先前获取的可配置的 ConfigurableListableBeanFactory 来配置该工厂的标准上下文特征。

例如设置:

  • 上下文的 ClassLoader

  • SpEL 表达式解析器 StandardBeanExpressionResolver

  • Bean 属性编辑器注册商 ResourceEditorRegistrar

    该注册商使用资源编辑器填充给定 PropertyEditorRegistry 注册表(通常是 org.springframework.beans.BeanWrapper),以用于在 ApplicationContext 应用上下文中创建 bean。

    BeanWrapper 是 Spring JavaBeans 的低级基础设施的中央接口,该类提供分析和操作标准 JavaBean 的操作:获取和设置属性值(单独或批量,支持无限深度的嵌套属性设置)、获取属性描述符以及查询属性的可读性/可写性的能力。

    Spring 使用 PropertyEditor 的概念来实现 Object 和 String 之间的转换。

  • Aware 后处理器 ApplicationContextAwareProcessor

    该后处理器会在初始化前为实现相应 Aware 感知能力接口的 Bean 设置相应的实例。

  • 监听器检测后处理器 ApplicationListenerDetector

    用于在早期检测内部 bean 是否为 ApplicationListener 并注册该监听器。

invokeBeanFactoryPostProcessors

该阶段调用之前注册到 BeanFactory 的 Bean 工厂后处理器 BeanFactoryPostProcessorpostProcessBeanFactory 方法,并将可配置的 ConfigurableListableBeanFactory 传递给 Bean。

如果这个工厂后处理器是一个 BeanDefinitionRegistryPostProcessor ,则会先调用其 postProcessBeanDefinitionRegistry 以允许实现向 BeanFactory 注册 BeanDefinition Bean 定义或修改已有的 Bean 定义。

像 Spring-Mybatis 就是实现了该 BeanDefinitionRegistryPostProcessor 接口,在项目启动时扫描 Mapper 接口将其注册为 Spring 的 Bean,来管理 Mapper 的生命周期。可以参考:Mybatis-快速入门 的 Spring Mybatis Mapper 注册机制。

BeanDefinitionRegistryPostProcessor 的实现里面有几个核心的类,需要注意一下:

  • ConfigurationClassPostProcessor

    BeanFactoryPostProcessor 用于带有 @Configuration 配置注解类的引导处理。

    在后置处理过程中,会解析该配置类,尝试解析其 @PropertySource@ComponentScan@Import@ImportResource、内部的 @Bean 方法、接口上的默认方法、父类等来获取 Bean 定义元数据。

    像我们平时 Spring Boot 的入口类,一般都会用 @SpringBootApplication 复合注解来标注,该复合注解主要由以下三个注解组成:

    • @SpringBootConfiguration
    • @EnableAutoConfiguration
    • @ComponentScan

    @SpringBootConfiguration 也是一个 @Configuration 注解,它会被后置处理器处理解析。

    并使用其 @EnableAutoConfiguration 注解上 @ImportAutoConfigurationImportSelector 来使用 EnableAutoConfiguration SPI 从该供应商处获得定义,我们的自动配置类一般都是通过该 SPI 定义给 Spring Boot,Import 内部又会递归处理其它的 Import,最终加载所有的配置类定义;

    @EnableAutoConfiguration 注解上 @AutoConfigurationPackage@Import 中定义的 ImportBeanDefinitionRegistrar 注册商会从定义的包名和类名来获取定义;

    @ComponentScan 来扫描路径获取定义;

Bean 生命周期

要与容器的 bean 生命周期管理进行交互,可以实现 Spring InitializingBeanDisposableBean 接口。容器为前者调用 afterPropertiesSet(),为后者调用destroy(),让 bean 在初始化和销毁 bean 时执行某些操作。

JSR-250 @PostConstruct@PreDestroy 注释通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的 bean 不会耦合到特定于 Spring 的接口。

如果您不想使用 JSR-250 注释但仍想删除耦合,请考虑使用 init-methoddestroy-method 对象定义元数据。 要让注入 Bean 调用指定的方法,可以在注解 @Bean 中指定 initMethoddestroyMethod 的方法名。 当这三种配置同时存在时,他们的执行顺序如下,销毁相同:

  1. 使用 @PostConstruct 注释的方法
  2. InitializingBean 回调接口定义的 afterPropertiesSet()
  3. 自定义的 initMethod 方法 当我们研究 Spring bean 的生命周期时,我们可以从对象实例化到它们的销毁等多个阶段来看。

为了保持简单,我们将它们分组为创建和销毁阶段

让我们更详细地解释这些阶段:

Bean 创建阶段

  • Instantiation - 实例化: Bean 的一切都是从这里开始的。Spring 实例化 bean 对象,就像我们手动创建 Java 对象实例一样。
  • Populating Properties - 填充属性: 实例化对象后,Spring 扫描实现 Aware 接口的 bean,并开始设置相关属性。
  • Pre-Initialization - 前置初始化: Spring 的 BeanPostProcessors 将在这个阶段开始工作。postProcessBeforeInitialization() 方法完成它们的工作。另外,带 @PostConstruct 注解的方法会在它们之后运行。
  • AfterPropertiesSet - 填充属性后: Spring 执行实现了 InitializingBean 的 bean 的 afterPropertiesSet() 方法。
  • Custom Initialization - 自定义初始化: Spring 会触发我们在 @Bean 注解的 initMethod 属性中定义的初始化方法。
  • Post-Initialization - 初始化后: Spring 的 BeanPostProcessors 第二次运行了。此阶段触发 postProcessAfterInitialization() 方法。

Bean 销毁阶段

  • Pre-Destroy - 销毁前: Spring 在这个阶段触发 @PreDestroy 注释的方法。
  • Destroy - 销毁: Spring 执行实现了DisposableBeandestroy() 方法。
  • Custom Destruction - 自定义解构: 我们可以在 @Bean 注解中用 destroyMethod 属性定义自定义销毁钩子,Spring 在最后阶段运行它们。

启动和停止应用回调

启动

在 SpringApplication 的启动阶段,会在最后一部调用 Runner,这里包含 ApplicationRunnerCommandLineRunner,我们可以注册该类型的 Bean 实现来实现启动时的逻辑处理。

如果想为我们的 Bean 实现启动或销毁时的逻辑处理,可以实现 Lifecycle 接口。

Lifecycle 接口为具有自己的生命周期要求的任何对象定义了基本方法(例如启动和停止某些后台进程)。 当 ApplicationContext 接到开始或停止信号时(比如,运行时的停止/重启场景),他将级联调用上下文中的所有 Lifecycle 实现。具体调用会委托给 LifecycleProcessor 完成。 默认的 Lifecycle 并没有细粒化控制自动启动,如果要对特定的 bean 自动启动,需要实现 SmartLifecycleDefaultLifecycleProcessor 默认处理会在应用启动时检测自动启动的 Bean,并调用 start 方法。 depends-on 定义了显式的依赖,但是有时候我们只知道前后,并不确定具体依赖顺序。 SmartLifecycle 接口定义了另外一种方法 getPhase() 。 启动时,phase 越小的对象最先启动;停止时,越大的越先停止。 默认的 Lifecycle 的 phase 是 0,因此,负值表示这个对象比标准组件启动快。

停止

在非 web 项目中,可以通过在 JVM 中注册关闭钩子实现关闭回调,registerShutdownHook() 方法定义在 ConfigurableApplicationContext 接口中。

同样要为 Bean 实现应用销毁时的监听,可以实现 Lifecycle 接口,并实现 destroy 逻辑。