老生常谈:聊聊Spring Boot中的那些生命周期和其中的可扩展点
- 前言
- 可扩展点的种类
- Spring Boot启动过程
- 1.SpringApplication的启动过程
- 2.ApplicationContext的启动过程
- 3.一般的非懒加载单例Bean在Spring Boot启动过程中的生命周期
- Spring Boot结束过程
- 1.SpringApplication启动时的异常处理过程
- 2.ApplicationContext的关闭过程
- 3.单例Bean销毁过程
- 总结
- 参考文献
前言
Spring框架的IoC特性对IoC容器中的对象进行了统一的管理,一个对象从创建到销毁所要经历的一系列步骤和过程都由IoC容器进行了定义和管理,这一系列步骤通常被称为Bean的生命周期。Bean的生命周期可以大致分为两个部分:创建和销毁。而创建过程又可大致分为:1)实例化(Instantiation,为对象分配内存);2)初始化(Initialization,设置对象属性)。
可扩展点
可扩展点
可扩展点
可扩展点
开始
实例化
初始化
销毁
结束
图1 bean生命周期
为什么要这样对Bean的生命周期进行划分呢?一方面是因为这些步骤对Bean的状态进行了实质性的改变,经过这些步骤,Bean和之前大不相同了;另一方面,从应用的角度讲,也是因为Spring框架在这些步骤的前后都埋下了可扩展点,可供用户进行一些定制化的操作,这样划分有助于我们理解这些定制化操作的时机的含义。
但是对于Spring Boot来讲,用户可用的可扩展点不仅仅只存在于Bean的生命周期当中。能够影响到最终产出的Bean的某些操作,也不仅仅只存在于Bean的生命周期当中,如BeanFactoryPostProcessor在Bean实例化之修改了BeanDefinition。这些与Bean生命周期无关的可扩展点,往往与应用(SpringApplication)和容器(ApplicationContext)在其启停过程中所要必须经历的一些步骤相关。在这里我们拓宽一下生命周期的概念,把应用和容器从创建到销毁所要经历的一系列必经的步骤叫做其生命周期。这样通过梳理SpringApplication和ApplicationContext的生命周期,我们就可以了解到与应用和容器启停相关的可扩展点,并进行一些定制化的操作。
图2 应用、容器和非懒加载单例Bean的创建顺序关系
本文基于SpringBoot 2.1.4.RELEASE对SpringApplication、ApplicationContext的启停过程和Bean的生命周期进行大致的梳理(Bean的生命周期梳理侧重于非懒加载单例Bean),目的是找出其中可供用户进行定制操作的扩展点,并梳理下容器启停过程和Bean生命周期的关联。
可扩展点的种类
Spring框架中预留的扩展方式主要有两种:实现特定接口的特定方法,该方法会在特定时机运行;继承父类,并重写父类在特定时机运行的方法。对于第二种方式,理论上来讲所有public和protected方法都可被子类重写而达到定制的目的,但实际上Spring Boot已经对大部分的方法写好了默认实现,这些实现也正是定义SpringBoot启停过程的基石。所以只有小部分方法是专门预留给子类进行扩展的,这些方法会在后续的梳理中加以区分。
扩展方式可细分为:
1)。也就是通过配置或注解标识的init-method和desctroy-method。
2)。实现该接口的Bean会在其生命周期或容器启停的某个时机调用该接口方法,根据应用范围还可细分为容器级别接口和Bean级别接口。容器级别接口一般在容器的尺度上进行操作,如BeanPostProcessor可对容器中的所有Bean进行操作;而Bean级别接口只会对实现该接口的Bean进行操作,典型的包括Aware系列接口,以及InitializingBean。
3)。需要配置是因为这些接口执行的时机在BeanDefinition导入容器之前,还不能通过BeanDefinition来生成实例,所以需要特殊配置一下来提前生成实例。
4)。用于对SpringApplication和ApplicationContext的子类实现扩展逻辑,没有默认实现。
5)。该类方法一般在父类已有默认实现,但子类也可添加额外的定制逻辑。
6)。该类方法理论上可以由子类重写,但该类方法的默认实现一般都包含了Spring Boot启动的必要逻辑,一般不建议重写。
Spring Boot启动过程
图3 Spring Boot启动过程中的可扩展点
Spring Boot启动过程及其中的可扩展点由上图所示,图中各个单元之间的箭头表示调用或顺次调用的意思,三条泳道分别代表应用、容器和Bean的启动过程。
1.SpringApplication的启动过程
的starting方法。实际上的各个方法的调用时机标志了SpringApplication启动过程的各个关键节点。
图4 SpringApplicationRunListener接口方法标志了SpringApplication启停的各个阶段
的starting方法,会触发事件发布器EventPublishingRunListener发布ApplicationStartingEvent事件。监听该类型事件的接口会调用其onApplicationEvent方法。
2)创建并配置完Environment(profile和properties信息)后,调用的environmentPrepared方法,同样会触发事件发布器发布ApplicationEnvironmentPreparedEvent,调用的onApplicationEvent方法。
3)接下来就开始了容器的创建和配置。首先进行ApplicationContext的实例化,随后调用,可对ApplicationContext进行部分初始化操作,该方法可由子类添加定制的容器初始化逻辑。
4)调用,该方法的默认实现会调用接口的initialize方法,用于对ApplicationContext进行初始化。
5)完成ApplicationContext的初始化后,
调用的contextPrepared方法,发布ApplicationContextInitializedEvent并调用相应的onApplicationEvent方法。
6)调用,把SpringApplication初始化时导入的资源注册到ApplicationContext中统一管理。这样ApplicationContext就对其生前发生的事情有了足够的了解。
7)调用的contextLoaded方法,发布ApplicationPreparedEvent并调用相应的onApplicationEvent方法。
8)调用,其中调用了,开始Bean的注入过程,并注册结束钩子用于容器销毁。
9)调用。该方法由子类实现,用于在容器刷新完成后进行一些应用级别的操作。
10)调用的started方法,发布ApplicationStartedEvent并调用相应的onApplicationEvent方法。此时容器已启动完成。
11)调用和的run方法。用于在容器启动后执行定制操作。
12)调用的running方法,发布ApplicationReadyEvent并调用相应的onApplicationEvent方法。完成应用的启动阶段。
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
//1)调用SpringApplicationRunListener的starting方法
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//2)调用SpringApplicationRunListener的environmentPrepared方法
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//3)实例化ApplicationContext
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//3)~7)调用SpringApplication的prepareContext方法
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//8)调用SpringApplication的refresh方法
refreshContext(context);
//9)调用SpringApplication的afterRefresh方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//10)调用SpringApplicationRunListener的started方法
listeners.started(context);
//11)调用ApplicationRunner和CommandLineRunner的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//12)调用SpringApplicationRunListener的running方法
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
图5 SpringApplication启动代码主体
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
//3)调用SpringApplication的postProcessApplicationContext
postProcessApplicationContext(context);
//4)调用SpringApplication的applyInitializers方法
applyInitializers(context);
//5)调用SpringApplicationRunListener的contextPrepared方法
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//6)调用SpringApplication的load方法
load(context, sources.toArray(new Object[0]));
//7)调用SpringApplicationRunListener的contextLoaded方法
listeners.contextLoaded(context);
}
图6 SpringApplication的prepareContext方法
2.ApplicationContext的启动过程
ApplicationContext的启动过程已AbstractApplicationContext的refresh方法为主体,其中涉及到的步骤可大致概括为以下几类:
1.ApplicationContext的实例化。
2.ApplicationContext的初始化,包括其核心组件BeanFactory的初始化,以及其他组件(MessageSource、事件广播器)的初始化。
3.非懒加载单例bean的创建。
图7 ApplicationContext的启动过程
而在ApplicationContext和BeanFactory的创建过程中,框架提供了一些扩展点供用户进行定制操作,如修改BeanFactory管理的BeanDefinition信息,以及容器创建过程中的后处理操作等。具体步骤如下:
1)在SpringApplication的启动过程的2)之后,进行ApplicationContext的实例化。
2)接下来会在SpringApplication的启动过程的3)和4)中进行部分初始化(后处理)操作。即和。其中会调用。
3)在SpringApplication的启动过程的8),调用,即调用,进行ApplicationContext组件的初始化以及开始Bean的注入过程。
(1)调用,其中会调用子类的对配置资源进行一些定制的初始化处理。
(2)调用,该方法调用了,它们由子类实现,来获取不同ApplicationContext的不同的BeanFactory(实例化或初始化BeanFactory)。
(3)调用,对beanFactory进行一些初始化操作。
(4)调用。该方法是预留扩展的beanFactory后处理方法。
(5)调用。其中会调用的postProcessBeanDefinitionRegistry方法和的postProcessBeanFactory方法。其中有后处理器从配置或注解中获取BeanDefinition并注册到容器上,并进行定制修改。自此就可以通过BeanDefinition来创建Bean了。
(6)调用。该方法把BeanPostProcessor注册到beanFactory,为后续步骤中的Bean创建添加后处理器。
(7)调用。把实现MessageSource接口的类作为AbstractApplicationContext的成员变量。
(8)调用。设置AbstractApplicationContext的事件广播器。后续的事件广播就由SpringApplication移交给ApplicationContext来做了。
(9)调用。该方法由可由子类实现,在实例化非懒加载单例bean之前来初始化一些特殊的、需要提前初始化的bean。
(10)调用。把SpringApplication的listener交给ApplicationContext管理。
(11)调用。初始化剩余的未初始化的所有非懒加载单例bean。然后会调用的afterSingletonsInstantiated方法。该方法专用于非懒加载单例bean在其创建完成后所要进行的定制操作。
(12)调用。其中会调用的onRefresh方法,该方法的默认实现会调用的start方法。随后发布ContextRefreshedEvent并调用相应的onApplicationEvent方法。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//(1)Prepare this context for refreshing.
prepareRefresh();
// (2)Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// (3)Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// (4)Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// (5)Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// (6)Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// (7)Initialize message source for this context.
initMessageSource();
// (8)Initialize event multicaster for this context.
initApplicationEventMulticaster();
// (9)Initialize other special beans in specific context subclasses.
onRefresh();
// (10)Check for listener beans and register them.
registerListeners();
// (11)Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// (12)Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
//exception handle...
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
图8 ApplicationContext的启动主体:refresh方法
3.一般的非懒加载单例Bean在Spring Boot启动过程中的生命周期
由前言所述,Bean的生命周期可以大致分为两个部分:创建和销毁。而创建过程又可大致分为:1)实例化;2)初始化。但如果把Bean的创建过程放在应用启动的背景来看,在其实例化之前还有BeanDefinition的创建过程,所以综合来看,Bean的创建过程可扩展为以下步骤:
可扩展点
可扩展点
可扩展点
可扩展点
开始
生成BeanDefinition
Bean实例化
Bean属性注入
结束
图9 Bean创建流程
中,会调用的postProcessBeanDefinitionRegistry方法。其中有个重要的BeanDefinitionRegistryPostProcessor:根据注解或xml配置生成BeanDefinition并注册到BeanDefinitionRegistry。
2)在ApplicationContext启动过程中的3)(5)中,调用的postProcessBeanFactory方法,对BeanDefinition做定制化修改。
3)接下来的步骤3)到步骤12)都是在ApplicationContext启动过程中的3)(11)即中进行bean的实例化和初始化操作。在进行实例化之前,首先调用的postProcessBeforeInstantiation方法。该方法可跳过后续实例化步骤,并包办实例化初始化过程,可用于创建代理类。
4)方法,可用来选择用于实例化的构造方法。
5)实例化bean,然后调用的postProcessMergedBeanDefinition方法。该方法提供了在bean的生命周期中修改BeanDefinition的途径,而步骤2)提供了在ApplicationContext启动时修改BeanDefinition的途径。
6)调用的postProcessAfterInstantiation方法。此时bean已经实例化,但属性还未设置。这里可以实现定制的属性注入逻辑,并跳过默认实现的属性注入步骤。
7)从BeanDefinition获取要注入的属性值。调用的postProcessProperties和postProcessPropertyValues方法对其进行定制处理。
8)注入属性值,调用的setBeanName方法、的setBeanClassLoader方法、的setBeanFactory方法,使bean获取BeanName、BeanClassLoader、BeanFactory进行相应操作。
9)调用的postProcessBeforeInitialization方法。此时属性值已注入,init方法尚未调用。其中有个ApplicationContextAwareProcessor调用了各种Aware接口方法。
10)调用的afterPropertiesSet方法。该方法一般用于对bean实例的配置进行校验,或在属性值注入后进行最终的初始化操作。
11)调用BeanDefinition中设置的(可以由@Bean的initMethod属性设置,也可由xml配置文件的init-method设置。
12)调用的postProcessAfterInitialization方法。此时Bean的初始化过程已经完成。
13)在ApplicationContext启动过程中的3)(12),会调用的onRefresh方法,该方法的默认实现会调用的start方法。该方法一般可用于开启一些异步过程。
注意:bean生命周期的步骤5),可以选用用Supplier、FactoryMethod和autowireConstructor进行实例化,bean生命周期与上述步骤略有不同。此外,若bean实现了FactoryBean,生命周期中某些步骤也略有不同(待后续总结)。
Spring Boot结束过程
图10 Spring Boot异常处理和结束过程中的可扩展点
1.SpringApplication启动时的异常处理过程
当SpringApplication启动时出现异常后,需要处理如下几件事:错误码的生成、发布应用内事件、向用户报告异常和关闭容器。具体步骤如下:
1)在SpringApplication的启动过程中出现异常时,首先通过异常类型来获取错误码,并发布ExitCodeEvent并调用相应的onApplicationEvent方法。
2)调用的failed方法,发布ApplicationFailedEvent并调用相应的onApplicationEvent方法。
3)调用的reportException方法。该方法可定制化实现,用于报告启动错误信息。
4)接下来会调用,即容器关闭的过程。有关SpringApplication启动时的异常处理过程到此结束。
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters,
SpringApplicationRunListeners listeners) {
try {
try {
//1)错误码处理
handleExitCode(context, exception);
if (listeners != null) {
//2)发布事件
listeners.failed(context, exception);
}
}
finally {
//3)定制错误报告
reportFailure(exceptionReporters, exception);
if (context != null) {
//4)容器资源释放
context.close();
}
}
} catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
图11 SpringApplication启动时的异常处理过程:handleRunFailure方法
2.ApplicationContext的关闭过程
容器的关闭过程大致包括:发布事件、执行容器内bean生命周期的销毁部分、关闭容器。具体步骤如下:
1)当SpringApplication启动时出现异常,或程序结束调用关闭钩子时,会调用,开始关闭容器的过程。
2)首先会发布ContextClosedEvent并调用相应的onApplicationEvent方法。并由的isRunning方法的返回值决定是否去调用或的stop方法。
3)调用方法。该方法用beanFactory来销毁所有容器管理的bean,可由子类添加定制逻辑。
4)处理完beanFactory管理的bean后,调用,对ApplicationContext定制实现的BeanFactory做定制的关闭处理。
5)调用,该方法用于给子类实现定制的额外的关闭操作。
/**
* Actually performs context closing: publishes a ContextClosedEvent and
* destroys the singletons in the bean factory of this application context.
* <p>Called by both {@code close()} and a JVM shutdown hook, if any.
* @see org.springframework.context.event.ContextClosedEvent
* @see #destroyBeans()
* @see #close()
* @see #registerShutdownHook()
*/
protected void doClose() {
// Check whether an actual close attempt is necessary...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
LiveBeansView.unregisterApplicationContext(this);
try {
// 2) Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// 2) Stop all Lifecycle beans, to avoid delays during individual destruction.
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// 3) Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// 4) Close the state of this context itself.
closeBeanFactory();
// 5) Let subclasses do some final clean-up if they wish...
onClose();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Switch to inactive.
this.active.set(false);
}
}
图12 ApplicationContext的关闭过程:doClose方法
3.单例Bean销毁过程
方法实现的,其中调用了ConfigurableBeanFactory的destroySingletons方法,具体由实现了ConfigurableBeanFactory接口的类来实现。Spring Boot的默认实现调用了DefaultSingletonBeanRegistry的destroySingletons进行销毁操作,其中会对实现了接口的bean进行定制操作。在默认实现中,每个都由DisposableBeanAdapter包装了一层,的销毁步骤实际上由DisposableBeanAdapter的destroy方法进行了制定,其具体步骤如下:
1)从容器的Bean列表中把Bean剔除后,调用的postProcessBeforeDestruction方法,用于在Bean销毁前对Bean进行处理。
2)调用的destroy方法。
3)调用BeanDefinition中设置的。
@Override
public void destroy() {
//1)调用DestructionAwareBeanPostProcessor的postProcessBeforeDestruction方法
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
//2)调用DisposableBean的destroy方法。
if (this.invokeDisposableBean) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
}
try {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((DisposableBean) this.bean).destroy();
return null;
}, this.acc);
}
else {
((DisposableBean) this.bean).destroy();
}
}
catch (Throwable ex) {
//异常日志处理
}
}
//3)调用BeanDefinition中设置的destroyMethod
if (this.destroyMethod != null) {
invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method methodToCall = determineDestroyMethod(this.destroyMethodName);
if (methodToCall != null) {
invokeCustomDestroyMethod(methodToCall);
}
}
}
图13 DisposableBeanAdapter的destroy方法
总结
接口进行bean的定制初始化操作,或实现对所有bean进行相关操作等等。而需在spring.factories中配置的接口和需要在通过重写SpringApplication及AbstractApplicationContext的方法则侧重于在容器启动过程中进行定制操作,这些方式更多地被Spring Boot框架本身采用,来实现框架各种丰富的功能。所以在应用启停过程中,除了核心的组件ApplicationContext和beanFactory,其他应用和容器所用的各种组件及后处理器,也为应用的正常运转和实现各种功能起到了重要的作用。本文从应用及开发的角度梳理了Spring Boot框架的启停步骤,但其实要想真正的学以致用,就应该了解一下框架本身是如何运用这些可扩展点来丰富框架功能的。下一篇文章中,我们就来聊聊Spring Boot框架中那些已经实现了的可扩展点的应用。