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)类似。
为了加深理解,我们来看一个实际使用的例子。在微服务系统中,存在两种类型的应用。一种是注册中心,另一种是客户端应用。注册中心负责管理所有的客户端应用,客户端应用则向外界提供服务。以 Spring Cloud 为例,Eureka 服务是一个注册中心,客户端应用则是基于 Spring Boot 的 Web 服务。客户端应用启动时需要完成两个操作,一是启动 Spring Boot 应用,也就是完成容器的创建。二是向 Eureka 服务注册自身的相关信息。
注册操作是一个比较复杂的行为,涉及到远程的网络请求。网络请求可能会遇到各种问题,就拿超时来说,可能是注册中心没有启动或者出现异常,也可能是网络延迟所导致的。因此注册操作允许重试,直到连接上注册中心为止。执行注册操作的组件是 EurekaAutoServiceRegistration
,实现了 Lifecycle
接口。感兴趣的读者可以自行查看源码,我们将在第三部 Spring Cloud 中进行详细分析。
3. 容器生命周期
3.1 概述
无论是 Bean 的生命周期,还是 Lifecycle 机制,我们讨论的对象都是单例。实际上,Spring 容器本身也存在生命周期。外层容器的生命周期驱动着内部组件生命周期的变化,而容器的生命周期又受外部环境的影响。所谓的外部环境有多种表现形式,列举几个比较常见的:
- 在 IDE 上点击启动或停止的图标
- 在命令行窗口中输入
java -jar
或kill
等命令 - 由 Servlet 容器或虚拟机在适当的时机触发
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 的初始化操作可以异步的,适合耗时较长的操作。
容器的生命周期包括初始化和关闭两个操作,初始化其实就是容器刷新流程,容器关闭有两种途径。一是手动调用 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 (+) 注:+号表示新增、*表示修改