【重写SpringFramework】生命周期管理(chapter 3-10)

1. 前言

在第一章 beans 模块中,我们介绍了 Bean 的生命周期,所有单例的初始化与销毁都是由 BeanFactory 驱动的。事实上,BeanFactory 本身也存在生命周期。而 BeanFactory 主要专注于管理单例,因此其自身的生命周期是委托给 ApplicationContext 来实现的。

本节我们将讨论以下几个问题,容器的生命周期是如何驱动的?容器的生命周期又如何传导到具体组件的?Spring 框架设计 Lifecycle 组件的初衷是什么,又是如何与现有体系进行互补的?

2. Lifecycle 组件

2.1 概述

Lifecycle 组件都是单例,而 BeanFactory 已经有了一套针对 Bean 的管理机制,包括依赖注入、指定初始化和销毁操作等。Lifecycle 组件作为一种特殊的单例,必然存在着通用处理逻辑不能覆盖的场景。我们可以将 Lifecycle 组件的特点归纳为两点,一是独立性,二是异步操作。

BeanFactory 接口中定义的所有 getBean 方法都抛出异常,而 getBean 方法会触发对象的创建流程。在此期间,实例化、填充对象、初始化等任何操作都有可能抛出异常。接下来的问题是,谁来处理这些异常?实际上这些异常是在 AbstractApplicationContext 的 refresh 方法中被捕获,执行一些销毁操作,然后再次抛出异常,最终导致的结果就是 Spring 容器创建失败。

 

java

代码解读

复制代码

public abstract class AbstractApplicationContext { @Override public void refresh() { try { //初始化操作,略 } catch(Exception e) { destroyBeans(); //销毁已创建的单例 throw e; //抛出异常,容器创建失败 } } }

从这里可以看出,普通单例与容器的关系十分密切,任何一个单例的初始化失败,都会导致容器创建失败。作为对比 Lifecycle 组件在执行 start 方法时,允许出现某些异常而不影响容器的创建,这就是所谓的独立性。第二个特点是异步性。异步操作可以看做是独立性的延申,相对独立意味着能以异步的方式来运行,适合执行一些耗时的操作。

2.2 相关 API

Lifecycle 接口定义了初始化和销毁操作,start 和 stop 方法都没有抛出异常,对比 InitializingBean 和 DisposableBean 接口定义的方法都抛出了异常。

 

java

代码解读

复制代码

public interface Lifecycle { void start(); void stop(); boolean isRunning(); }

LifecycleProcessor 接口的作用是管理 Lifecycle 组件,onRefresh 和 onClose 方法会将启动和停止的信号传递给所有的 Lifecycle 组件。

 

java

代码解读

复制代码

public interface LifecycleProcessor{ void onRefresh(); void onClose(); }

DefaultLifecycleProcessor 是 LifecycleProcessor 接口的默认实现类,持有一个 BeanFactory 的实例。先来看 onRefresh 方法,调用 getLifecycleBeans 方法寻找实现了 Lifecycle 接口的单例,然后依次遍历并执行 start 方法。onClose 方法的实现与之类似,也是遍历容器中的所有 Lifecycle 组件,并调用 stop 方法。

这边整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】免费获取

java

代码解读

复制代码

public class DefaultLifecycleProcessor implements LifecycleProcessor { private ConfigurableBeanFactory beanFactory; @Override public void onRefresh() { Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); for (Map.Entry<String, Lifecycle> entry : lifecycleBeans.entrySet()) { Lifecycle bean = entry.getValue(); if(!bean.isRunning()){ bean.start(); } } } @Override public void onClose() { Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); for (Lifecycle lifecycle : lifecycleBeans.values()) { lifecycle.stop(); } } private Map<String, Lifecycle> getLifecycleBeans() { //遍历Spring容器,寻找实现了Lifecycle接口的单例 Map<String, Lifecycle> beans = new LinkedHashMap<>(); List<String> beanNames = this.beanFactory.getBeanNamesForType(Lifecycle.class); for (String beanName : beanNames) { Lifecycle bean = this.beanFactory.getBean(beanName, Lifecycle.class); beans.put(beanName, bean); } return beans; } }

2.3 深入思考

我们已经知道了 Lifecycle 组件的特点,那么它是否是必须的呢?换句话说,能否在 Bean 的初始化过程中执行异步操作吗?答案是不行。试想一下,如果某个单例的初始化操作是异步的,并且需要耗费一定的时间。与此同时,Spring 容器创建成功,开始对外提供服务。然后这个单例的异步任务执行失败,Spring 容器又会被关闭,这种情况是绝对不允许发生的。归根结底,Spring 在设计 Bean 的生命周期时,期望的是任何单例初始化失败,需抛出异常,终止容器的创建,这一点与 Java 集合中的快速失败机制(fail-fast)类似。

10.1 微服务运行原理图.png

为了加深理解,我们来看一个实际使用的例子。在微服务系统中,存在两种类型的应用。一种是注册中心,另一种是客户端应用。注册中心负责管理所有的客户端应用,客户端应用则向外界提供服务。以 Spring Cloud 为例,Eureka 服务是一个注册中心,客户端应用则是基于 Spring Boot 的 Web 服务。客户端应用启动时需要完成两个操作,一是启动 Spring Boot 应用,也就是完成容器的创建。二是向 Eureka 服务注册自身的相关信息。

注册操作是一个比较复杂的行为,涉及到远程的网络请求。网络请求可能会遇到各种问题,就拿超时来说,可能是注册中心没有启动或者出现异常,也可能是网络延迟所导致的。因此注册操作允许重试,直到连接上注册中心为止。执行注册操作的组件是 EurekaAutoServiceRegistration,实现了 Lifecycle 接口。感兴趣的读者可以自行查看源码,我们将在第三部 Spring Cloud 中进行详细分析。

3. 容器生命周期

3.1 概述

无论是 Bean 的生命周期,还是 Lifecycle 机制,我们讨论的对象都是单例。实际上,Spring 容器本身也存在生命周期。外层容器的生命周期驱动着内部组件生命周期的变化,而容器的生命周期又受外部环境的影响。所谓的外部环境有多种表现形式,列举几个比较常见的:

  • 在 IDE 上点击启动或停止的图标
  • 在命令行窗口中输入 java -jar 或 kill 等命令
  • 由 Servlet 容器或虚拟机在适当的时机触发

10.2 Spring 容器生命周期.png

3.2 容器的初始化

容器的初始化就是 Spring 容器的创建过程,核心流程就是 AbstractApplictionContext 的 refresh 方法。虽然一直没有强调容器的初始化这一概念,但本章的大多数内容都与之有关。我们已经介绍了很多机制,这里只关注 Lifecycle 组件是如何初始化的。

在 refresh 方法中,finishRefresh 方法是最后的步骤,创建了 DefaultLifecycleProcessor 实例,并调用 onRefresh 方法即可。Spring 将 Lifecycle 组件的初始化放在了整个流程中非常靠后的位置,仅次于发布事件,从这一点也反应出 Lifecycle 组件是相对独立的。

 

java

代码解读

复制代码

public abstract class AbstractApplicationContext { private LifecycleProcessor lifecycleProcessor; protected void finishRefresh() { this.lifecycleProcessor = new DefaultLifecycleProcessor(getBeanFactory()); this.lifecycleProcessor.onRefresh(); //发送ContextRefreshedEvent事件(TODO) } }

3.3 容器的关闭

关闭容器的操作定义在 ConfigurableApplicationContext 接口中。触发容器关闭有两种方式,close 方法表示手动关闭,registerShutdownHook 方法则是向虚拟机注册一个钩子函数,在虚拟机停止运行之前,以回调的方式通知 Spring 容器,执行销毁流程。在实际使用中,传统的 Web 应用属于第一种方式,而 Spring Boot 应用则是第二种方式。

 

java

代码解读

复制代码

public interface ConfigurableApplicationContext { void close(); void registerShutdownHook(); }

AbstractApplicationContext 实现了这两种关闭方式,registerShutdownHook 方法的核心是向虚拟机注册了一个异步操作。close 方法需要注意的是,移除可能存在的钩子函数,避免重复执行 Spring 容器销毁操作。registerShutdownHook 和 close 方法只是触发的条件不同,实际的销毁操作都是由 doClose 方法完成的。

 

java

代码解读

复制代码

public abstract class AbstractApplicationContext { private Thread shutdownHook; @Override public void registerShutdownHook() { if (this.shutdownHook == null) { this.shutdownHook = new Thread() { @Override public void run() { synchronized (startupShutdownMonitor) { doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } } @Override public void close() { synchronized (this.startupShutdownMonitor) { //执行实际关闭逻辑 doClose(); //移除已注册的虚拟机钩子,close方法已经显式地关闭了上下文 if (this.shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } } } }

在 doClose 方法中,首先检查 active 和 closed 两个标记位,确保容器的状态是活跃且未关闭的。接下来主要执行了以下四步,代码都比较简单,仅罗列如下:

  • 调用 publishEvent 方法,发布容器关闭事件(先略过)。
  • 调用 LifecycleProcessor 的 onClose 方法,销毁 Lifecycle 组件。
  • 调用 destoryBeans 方法,销毁所有的单例 Bean,实际调用了 BeanFactory 的 destroySingletons 方法。
  • 调用 onClose 方法,子类可以完成一些自定义操作。比如在 Spring Boot 应用中,EmbeddedWebApplicationContext 关闭了内嵌的 Servlet 容器。
 

java

代码解读

复制代码

protected void doClose() { if (this.active.get() && this.closed.compareAndSet(false, true)) { //1. 发送容器关闭事件(TODO) //2. 销毁Lifecycle组件 if (this.lifecycleProcessor != null) { this.lifecycleProcessor.onClose(); } //3. 销毁单例Bean destroyBeans(); //4. 扩展方法,由子类实现 onClose(); this.active.set(false); } }

4. 测试

4.1 手动关闭容器

回顾之前与 ApplicationContext 有关的测试方法,我们只调用了 refresh 方法,实际上容器并没有执行关闭流程,而是在代码执行完毕后被虚拟机回收了。本次测试有两个目的,一是触发容器的关闭操作,二是观察 Lifecycle 组件的生命周期的变化。首先定义一个 Lifecycle 组件,在 start 和 stop 方法中打印日志。

 

java

代码解读

复制代码

//测试类,Lifecycle组件的简单实现 public class LifeCycleBean implements Lifecycle { @Override public void start() { System.out.println("启动Lifecycle组件..."); } @Override public void stop() { System.out.println("销毁Lifecycle组件..."); } }

在测试方法中,调用 refresh 方法刷新容器,执行 Lifecycle 组件的初始化操作。最后调用 close 方法关闭容器,在此过程中,LifecycleProcessor 会通知所有的 Lifecycle 组件执行销毁操作。

 

java

代码解读

复制代码

//测试方法 @Test public void testCloseMethod() { GenericApplicationContext context = new GenericApplicationContext(); context.registerBeanDefinition("lifecycleBean", new RootBeanDefinition(LifeCycleBean.class)); context.refresh(); //手动触发 context.close(); }

从测试结果可以看出,LifeCycleBean 分别执行了初始化和销毁操作。

 

erlang

代码解读

复制代码

启动Lifecycle组件... 销毁Lifecycle组件...

4.2 自动关闭容器

在测试方法中,调用 registerShutdownHook 方法向虚拟机注册钩子函数。当代码执行完毕,虚拟机在退出之前,以回调的方式通知容器执行关闭操作。

 

java

代码解读

复制代码

//测试方法 @Test public void testJvmHook() { GenericApplicationContext context = new GenericApplicationContext(); context.registerBeanDefinition("lifecycleBean", new RootBeanDefinition(LifeCycleBean.class)); //注册虚拟机钩子,当程序结束运行 context.registerShutdownHook(); context.refresh(); }

测试结果与上个测试完全一致,说明手动关闭和自动关闭的效果是一样的。

 

erlang

代码解读

复制代码

启动Lifecycle组件... 销毁Lifecycle组件...

5. 总结

本节我们介绍了 Spring 的生命周期管理,包括容器的生命周期、Bean 的生命周期,以及 Lifecycle 机制。容器的生命周期由外部环境控制,容器本身驱动着 Bean 和 Lifecycle 组件的生命周期的改变。Bean 的生命周期和 Lifecycle 机制主要是为单例服务的,它们之间既有联系又有区别,以初始化操作为例简单说明:

  • Bean 的初始化操作在创建 Bean 的过程中执行,Lifecycle 的初始化操作在刷新容器的最后阶段执行。
  • Bean 的初始化操作与容器密切相关,一旦报错会立即终止容器的运行。Lifecycle 的初始化操作较为独立,即使报错也未必影响容器的运行。
  • Bean 的初始化是同步操作,应尽量减少耗时,使应用更快的启动。Lifecycle 的初始化操作可以异步的,适合耗时较长的操作。

10.3 生命周期管理.png

容器的生命周期包括初始化和关闭两个操作,初始化其实就是容器刷新流程,容器关闭有两种途径。一是手动调用 close 方法关闭容器,二是向虚拟机注册一个钩子函数,当虚拟机关闭的时候,触发容器的关闭操作。这两种关闭操作的效果是一样的,都会触发 Bean 和 Lifecycle 组件的销毁操作。

6. 项目信息

新增修改一览,新增(5),修改(2)。

 

scss

代码解读

复制代码

context └─ src ├─ main │ └─ java │ └─ cn.stimd.spring │ └─ context │ ├─ support │ │ ├─ AbstractApplicationContext.java (*) │ │ └─ DefaultLifecycleProcessor.java (+) │ ├─ ConfigurableApplicationContext.java (*) │ ├─ Lifecycle.java (+) │ └─ LifecycleProcessor.java (+) └─ test └─ java └─ context └─ lifecycle ├─ LifecycleBean.java (+) └─ LifecycleTest.java (+) 注:+号表示新增、*表示修改

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值