glmapper

SpringBoot 源码系列-启动过程分析

字数统计: 5.2k阅读时长: 20 min
2019/12/07 Share

个人博客:glmapper
更多请关注 glmapper工作室 微信公众号

SpringBoot 作为目前非常流行的微服务框架,它使得构建独立的 Spring 生产级应用变得非常简单,因此受到很多互联网企业的青睐。

最近在写 SOFATracer 集成 Spring Cloud Stream RocketMQ 的过程中,遇到了一些问题,比如:BeanPostProcessor 不生效,如何在 BeanPostProcessor 不生效的情况下去修改一个 Bean 等,这些问题其实都是和 Bean 的生命周期有关系的,当然也和容器启动的过程有关系。SpringBoot 的启动过程对于我来说其实不算陌生,也可以说是比较熟悉,但是之前没有完整的梳理过这一块的东西,在实际的应用过程成难免再去踩一些坑。另外想到之前也写过一篇 SpringBoot系列- FatJar 启动原理,刚好承接上篇,继续来探索 SpringBoot 中的一些知识点。

注:本篇基于 SpringBoot 2.1.0.RELEASE 版本,SpringBoot 各个版本之间可能存在差异,不过大体流程基本差不多,所以各位看官在实际的工作过程中也

从一份配置文件开始说起

Spring 的启动过程实际上就是 Ioc 容器初始化以及载入 Bean 的过程;SpringBoot 的启动过程最核心的容器刷新流程也是复用了 Spring 容器刷新的逻辑。在分析 SpringBoot 启动过程之前,我们先来简单回顾下 Spring web 应用基于 tomcat 容器部署的启动过程。这就需要从一个大家都熟悉的配置文件开始说起:

1
2
3
4
5
6
7
<listener>  
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

在一般的传统 WEB 项目中,项目的启动一般是从 web.xml 文件的载入开始,如果我们的项目中使用了Spring,那么你肯定会在你的 web.xml 文件中看到上面的配置。Spring 正是通过 ContextLoaderListener 监听器作为容器初始化入口的。

ContextLoaderListener 继承了 ContextLoader 类和 ServletContextListener 接口,并且重写了 ServletContextListener 中的contextInitialized 和 contextDestroyed 方法。在 contextInitialized 中,通过调用父类(ContextLoader)的 initWebApplicationContext 方法进行容器创建:

1
2
3
4
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

对于上述 Spring 容器引导刷新大概可以分为两个点来做简单的归纳:

  • 1、通过监听 ServletContextEvent 事件,为 web 容器提供一个全局的 ServletContext 上下文环境,并作为后面 spring 容器的宿主环境

  • 2、在 contextInitialized 方法被调用时,spring 开始初始化一个上下文,这个上下文被称为根上下文,也就是 WebApplicationContext(实际的实现类是 XmlWebApplicationContext )。这个 WebApplicationContext 就是 spring 的 IoC 容器,其对应的 Bean 定义的配置文件由 web.xml 中的 context-param 指定。

关于依赖监听 ServletContextEvent 事件来引导启动的过程大致可以描述为一下过程:

相对于通过监听 ServletContextEvent 事件方式引导刷新 Spring 上下文,SpringBoot 给我的感觉是回归了 java 的本源,即通过 main 方法方式引导启动。由于 SpringBoot 中对于 web 容器也是使用了嵌入式+自动配置的方式,所以在启动入口上差异还是比较大的,当然 SpringBoot 除了支持 fatjar 方式之外,也提供了 war 包方式来保持对原有 Spring 工程的兼容。

本篇文章将承接上一篇《SpringBoot FatJar 启动原理》,来分析下 SpringBoot 的启动过程。希望通过本篇文章,能够让大家了解到与传统基于 servlet 事件引导启动和基于 main 方式启动的不同,从而对 SpringBoot 的整体启动过程有比较清楚的认识。

启动入口

在这篇SpringBoot系列- FatJar 启动原理 文章中介绍得到,JarLaunch 最后是构建了一个 MainMethodRunner 实例对象,然后通过反射的方式调用了 BootStrap 类中的 main 方法,这里的 ’BootStrap 类中的 main 方法‘ 实际上就是 SpringBoot 的业务入口,也就是常见的下面的代码片段:

1
2
3
4
5
6
@SpringBootApplication
public class GlmapperApplication {
public static void main(String[] args) {
SpringApplication.run(GlmapperApplication.class, args);
}
}

从代码可以非常直观的了解到,启动是通过调用 SpringApplication 的静态方法 run;这个 run 方法内部其实是会构造一个 SpringApplication 的实例,然后再调用这里实例的 run 方法来启动 SpringBoot 的。

1
2
3
4
5
6
7
8
9
10
11
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

因此,如果要分析 SpringBoot 的启动过程,我们需要熟悉 SpringApplication 的构造过程以及 SpringApplication 的 run 方法执行过程即可。

SpringApplication 实例的构建

篇幅原因,我们只分析核心的构建流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源加载器,默认是 null
this.resourceLoader = resourceLoader;
// 启动类 bean
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 是否是 web 应用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置了 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 设置 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 启动类
this.mainApplicationClass = deduceMainApplicationClass();
}

上面代码段中,需要关注两个点:

  • 1、初始化 ApplicationContextInitializer;
  • 2、初始化 ApplicationListener

要注意的是这里的实例化,并非是通过注解和扫包完成,而是通过一种不依赖 Spring 上下文的加载方法;这种做法是为了能够使得在 Spring 完成启动前做各种配置。Spring 的解决方法是以接口的全限定名作为 key,实现类的全限定名作为 value 记录在项目的 META-INF/spring.factories 文件中,然后通过SpringFactoriesLoader 工具类提供静态方法进行类加载并缓存下来,spring.factories 是 SpringBoot 的核心配置文件。SpringFactoriesLoader 可以理解为 Spring 自己提供的一种 spi 扩展实现。SpringBoot 中提供的默认的 spring.factories 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
// ..省略
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
// ..省略
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
// ..省略
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\/
// ..省略
# Application Listeners
org.springframework.context.ApplicationListener=\
// ..省略
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
// ..省略
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
// ..省略
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
// ..省略

关于 SpringFactoriesLoader 如何加载这些资源这里就不过多分析,有兴趣的读者可以自行查看相关源码。org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

run 方法主流程

SpringApplication 的 run 方法 SpringBoot 进行 Spring 容器刷新的实际入口方法,这个方法中包括了很多 SpringBoot 自己扩展出来的一些特性机制,比如 SpringApplicationRunListener、打印启动 Banner、统一的异常处理扩展等等。下面就直观的看下代码,然后再逐个分析各个流程的具体细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public ConfigurableApplicationContext run(String... args) {
// 开启容器启动计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// SpringBootExceptionReporter 列表,SpringBoot 允许自定义 Reporter
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置java.awt.headless属性为true还是false
// 可详见解释:https://blog.csdn.net/michaelgo/article/details/81634017
configureHeadlessProperty();
// 获取所有 SpringApplicationRunListener ,也是通过 SpringFactoriesLoader 来获取的
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布 starting 事件,在首次启动 run方法时立即调用,可用于非常早的初始化,注意此时容器上下文还没有刷新
listeners.starting();
try {
// 构建 ApplicationArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 准备上下文刷新需要的环境属性 -- 详见 prepareEnvironment 过程分析
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// spring.beaninfo.ignore,如果为空设置为true
configureIgnoreBeanInfo(environment);
// 打印 SpringBoot 启动 Banner
Banner printedBanner = printBanner(environment);
// 创建上下文,这里会根据 webApplicationType 类型来创建不同的 ApplicationContext
context = createApplicationContext();
// 加载获取 exceptionReporters
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 上下文刷新之前的准备工作 -- 详见 prepareContext 过程分析
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新上下文 -- 详见 refreshContext 过程分析
refreshContext(context);
// 刷新之后回调,SpringBoot 中这个方法是空实现,可以自行扩展
afterRefresh(context, applicationArguments);
// 停止计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 发布 started 事件
listeners.started(context);
// ApplicationRunner 和 CommandLineRunner 调用
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
// 发布 running 事件
listeners.running(context);
}
catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

上面对代码基本都做了一些详细的注释,有几个需要关注的点:

  • 1、prepareEnvironment 的处理过程
  • 2、prepareContext 的处理过程
  • 3、refreshContext 的处理过程
  • 4、listeners 执行时机及顺序
  • 5、异常处理逻辑

关于 Listeners 执行时机及顺序在之前的文章中有做过非常详细的分析,详见:SpringBoot 系列-事件机制详解。下面就对其他的 4 个点做下详细的分析。

分析启动过程,本质上是对其整个容器生命周期有个了解,包括 listeners 执行各个事件的时机、PostProcessor 执行的时机,Enviroment Ready 的时机等等。掌握这些扩展和时机,可以在实际的业务开发中来做很多事情。

prepareEnvironment 的处理过程

prepareEnvironment 过程相对来说是比较早的,这里主要就是为上下文刷新提供 Environment。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置 PropertySources 和 Profiles
// 1、将参数和一些默认的属性配置到 environment
// 2、激活 profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
// 绑定 SpringApplication 环境
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 附加的解析器将动态跟踪底层 Environment 属性源的任何添加或删除
ConfigurationPropertySources.attach(environment);
return environment;
}

这里面做的事情就是将我们的配置,包括系统配置、application.properties、-D 参数等等统统打包给 environment。在 Spring 中,我们最常见的 xml 中使用的 ${xxx} 或者代码中使用的 @Value(“${xxxx}”) 等,最后都是从 environment 中拿值的。

这里需要关注的一个比较重要的点是发布 ApplicationEnvironmentPreparedEvent 事件,我们可以通过监听这个事件来修改 environment。这里可以参考下 SOFATracer 中 SofaTracerConfigurationListener 是如何利用这个事件来做环境配置处理的。

prepareContext 的处理过程

prepareContext 的处理过程中可以利用的点是非常多的,比如 ApplicationContextInitializer 的执行、ApplicationContextInitializedEvent 和 ApplicationPreparedEvent 事件发布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置 environment 给 context,所以需要注意的是,在此之前拿到的 context 中,environment 是没有的。
context.setEnvironment(environment);
// 对 ApplicationContext 的后置处理,比如注册 BeanNameGenerator 和 ResourceLoader
postProcessApplicationContext(context);
// 这里开始执行所有的 ApplicationContextInitializer
applyInitializers(context);
// 发布 ApplicationContextInitializedEvent 事件
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) {
// 是否允许 bean 覆盖,这里如果是 false ,则可能会导致 BeanDefinitionOverrideException 异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 发布 ApplicationPreparedEvent 事件
listeners.contextLoaded(context);
}

ApplicationContextInitializer 是 spring 容器刷新之前初始化 Spring ConfigurableApplicationContext 的回调接口,ApplicationContextInitializer 的 initialize 方法执行之前,context 是还没有刷新的。可以看到在 applyInitializers 之后紧接着发布了 ApplicationContextInitializedEvent 事件。其实这两个点都可以对 context 搞一些事情,ApplicationContextInitializer 更纯粹些,它只关注 context;而 ApplicationContextInitializedEvent 事件源中除了 context 之外,还有 springApplication 对象和参数 args。

prepareContext 最后阶段是发布了 ApplicationPreparedEvent 事件,表示上下文已经准备好了,可以随时执行 refresh 了。

refreshContext 的处理过程

refreshContext 是 Spring 上下文刷新的过程,这里实际调用的是 AbstractApplicationContext 的 refresh 方法;所以 SpringBoot 也是复用了 Spring 上下文刷新的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Override
public void refresh() throws BeansException, IllegalStateException {
// 加锁处理
synchronized (this.startupShutdownMonitor) {
// 准备刷新此上下文。主要包括占位符的替换及验证所有的 properties
prepareRefresh();
// 这里做了很多事情:
// 1、让子类刷新内部beanFactory ,创建IoC容器(DefaultListableBeanFactory--ConfigurableListableBeanFactory 的实现类)
// 2、加载解析XML文件(最终存储到Document对象中)
// 3、读取Document对象,并完成BeanDefinition的加载和注册工作
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对 beanFactory 进行一些预处理(设置一些公共属性)
prepareBeanFactory(beanFactory);

try {
// 允许在 AbstractApplicationContext的子类中对 BeanFactory 进行后置处理,postProcessBeanFactory()这个方法是个空实现。
postProcessBeanFactory(beanFactory);
// 调用 BeanFactoryPostProcessor 后置处理器处理 BeanFactory 实例(BeanDefinition)
invokeBeanFactoryPostProcessors(beanFactory);
// 注册BeanPostProcessor后置处理器,BeanPostProcessors后置处理器用于拦截bean的创建
// 用于对创建后的bean实例进行处理
registerBeanPostProcessors(beanFactory);
// 初始化消息资源
initMessageSource();
// 初始化应用事件广播器
initApplicationEventMulticaster();
// 初始化特殊的bean,这个方法是空实现,让AbstractApplicationContext的子类重写
onRefresh();
// 注册监听器(ApplicationListener)
registerListeners();
// 实例化剩余的单例bean(非懒加载方式), Bean的 IoC、DI 和 AOP 都是发生在此步骤
finishBeanFactoryInitialization(beanFactory);
// 完成刷新
// 1、发布 ContextRefreshedEvent 事件
// 2、处理 LifecycleProcessor
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();
}
}
}

这个过程涉及到的东西非常多,可扩展的点也非常多,包括 BeanFactoryPostProcessor 处理、BeanPostProcessor 处理、LifecycleProcessor 处理已经 发布 ContextRefreshedEvent 事件等。到这里容器刷新已经完成,容器已经 ready,DI 和 AOP 也已经完成。

BeanFactoryPostProcessor 处理

BeanFactoryPostProcessor 可以对我们的 beanFactory 内所有的 beandefinition(未实例化)数据进行修改,这个过程是在 bean 还没有实例化之前做的。所以在这,我们通过自己去注册一些 beandefinition ,也可以对 beandefinition 做一些修改。关于 BeanFactoryPostProcessor 的用法在很多框架中都有体现,这里以 SOFATracer 中修改 Datasource 为例来说明下。

SOFATracer 中为了对有所基于 jdbc 规范的数据源进行埋点,提供了一个 DataSourceBeanFactoryPostProcessor,用于修改原生 DataSource 来实现一层代理。代码详见:com.alipay.sofa.tracer.boot.datasource.processor.DataSourceBeanFactoryPostProcessor

这里只看核心代码部分,在 postProcessBeanFactory 方法中会根据 Datasource 的类型来创建不同的 DataSourceProxy;创建 DataSourceProxy 的过程就是修改原生 Datasource 的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void createDataSourceProxy(ConfigurableListableBeanFactory beanFactory,
String beanName, BeanDefinition originDataSource,
String jdbcUrl) {
// re-register origin datasource bean
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
// 先把之前已经存在的 Datasource 的 BeanDefinition 移除
beanDefinitionRegistry.removeBeanDefinition(beanName);
boolean isPrimary = originDataSource.isPrimary();
originDataSource.setPrimary(false);
// 换个 beanName ,重新注册到容器中
beanDefinitionRegistry.registerBeanDefinition(transformDatasourceBeanName(beanName),
originDataSource);
// 构建代理的 datasource BeanDefinition,类型为 SmartDataSource
RootBeanDefinition proxiedBeanDefinition = new RootBeanDefinition(SmartDataSource.class);
// 设置 BeanDefinition 相关属性
proxiedBeanDefinition.setRole(BeanDefinition.ROLE_APPLICATION);
proxiedBeanDefinition.setPrimary(isPrimary);
proxiedBeanDefinition.setInitMethodName("init");
proxiedBeanDefinition.setDependsOn(transformDatasourceBeanName(beanName));
// 获取原生 datasource 的属性值
MutablePropertyValues originValues = originDataSource.getPropertyValues();
MutablePropertyValues values = new MutablePropertyValues();
String appName = environment.getProperty(TRACER_APPNAME_KEY);
// 修改和新增属性
Assert.isTrue(!StringUtils.isBlank(appName), TRACER_APPNAME_KEY + " must be configured!");
values.add("appName", appName);
values.add("delegate", new RuntimeBeanReference(transformDatasourceBeanName(beanName)));
values.add("dbType",
DataSourceUtils.resolveDbTypeFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
values.add("database",
DataSourceUtils.resolveDatabaseFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
// 将新的 values 设置给代理 BeanDefinition
proxiedBeanDefinition.setPropertyValues(values);
// 将代理的 datasource BeanDefinition 注册到容器中
beanDefinitionRegistry.registerBeanDefinition(beanName, proxiedBeanDefinition);
}

上面这段代码就是 BeanFactoryPostProcessor 一种典型的应用场景,就是修改 BeanDefinition。

BeanFactoryPostProcessor 处理过程代码比较长,这里就不在具体分析处理的流程。需要关注的点是:1、BeanFactoryPostProcessor 的作用,它能做哪些事情;2、它是在容器启动的哪个阶段执行的。

registerBeanPostProcessors 的处理过程

registerBeanPostProcessors 是用于注册 BeanPostProcessor 的。BeanPostProcessor 的作用时机相对于 BeanFactoryPostProcessor 来说要晚一些,BeanFactoryPostProcessor 处理的是 BeanDefinition,Bean 还没有实例化;BeanPostProcessor 处理的是 Bean,BeanPostProcessor 包括两个方法,分别用于在 Bean 实例化之前和实例化之后回调。

开篇有提到,在某些场景下会出现 BeanPostProcessor 不生效。对于 Spring 来说,BeanPostProcessor 本身也会被注册成一个 Bean,那么自然就可能会出现,BeanPostProcessor 处理的 bean 在 BeanPostProcessor 本身初始化之前就已经完成了的情况。

registerBeanPostProcessors 大体分为以下几个部分:

  • 注册 BeanPostProcessorChecker。(当一个 bean 在 BeanPostProcessor 实例化过程中被创建时,即当一个bean没有资格被所有 BeanPostProcessor 处理时,它记录一个信息消息)
  • 实现优先排序、排序和其他操作的 BeanPostProcessor 之间进行排序
  • 注册实现 PriorityOrdered 的 BeanPostProcessor
  • 注册实现 Ordered 的
  • 注册所有常规的 BeanPostProcessor
  • 重新注册所有的内部 BeanPostProcessor
  • 将后处理器注册为用于检测内部 bean 的 applicationlistener,将其移动到处理器链的末端(用于获取代理等)。

这里还是以扩展时机为主线,Bean 的 IoC、DI 和 AOP 初始化过程不细究。

LifecycleProcessor 的处理过程

LifecycleProcessor 的处理过程是在 finishRefresh 方法中执行,下面先看下 finishRefresh 方法:

1
2
3
4
5
6
7
8
9
10
11
12
protected void finishRefresh() {
// 清除上下文级的资源缓存(比如扫描的ASM元数据)。
clearResourceCaches();
// 为此上下文初始化 LifecycleProcessor。
initLifecycleProcessor();
// 首先将 refresh 传播到 LifecycleProcessor。
getLifecycleProcessor().onRefresh();
// 发布 ContextRefreshedEvent 事件
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}

初始化 initLifecycleProcessor 是从容器中拿到所有的 LifecycleProcessor ,如果业务代码中没有实现 LifecycleProcessor 接口的 bean ,则使用默认的 DefaultLifecycleProcessor。

onRefresh 过程是 最后会调用到 Lifecycle 接口的 start 方法。LifeCycle 定义 Spring 容器对象的生命周期,任何 spring 管理对象都可以实现该接口。然后,当 ApplicationContext 本身接收启动和停止信号(例如在运行时停止/重启场景)时,spring 容器将在容器上下文中找出所有实现了 LifeCycle 及其子类接口的类,并一一调用它们实现的类。spring 是通过委托给生命周期处理器 LifecycleProcessor 来实现这一点的。Lifecycle 接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Lifecycle {
/**
* 启动当前组件
* 1、如果组件已经在运行,不应该抛出异常
* 2、对于容器,这将把开始信号传播到应用的所有组件
*/
void start();
/**
* 通常以同步方式停止该组件,当该方法执行完成后,该组件会被完全停止。当需要异步停止行为时,考虑实现 SmartLifecycle 和它的 stop
* (Runnable) 方法变体。注意,此停止通知在销毁前不能保证到达:在常规关闭时,{@code Lifecycle} bean将首先收到一个停止通知,然后才传播
* 常规销毁回调;然而,在上下文的生命周期内的热刷新或中止的刷新尝试上,只调用销毁方法。对于容器,这将把停止信号传播到应用的所有组件
*/
void stop();

/**
* 检查此组件是否正在运行。
* 1. 只有该方法返回 false 时,start方法才会被执行。
* 2. 只有该方法返回 true 时,stop(Runnable callback) 或 stop() 方法才会被执行。
*/
boolean isRunning();
}

至此,容器刷新其实已经就完成了。可以看到 Spring 或者 SpringBoot 在整个启动过程中,有非常多的口子暴露出来,供用户使用,非常灵活。

异常处理逻辑

与正常流程类似,异常处理流程同样作为 SpringBoot 生命周期的一个环节,在异常发生时,会通过一些机制来处理收尾过程。异常处理部分 SpringBoot 1.x 版本和 SpringBoot 2.x 版本差异还是比较大的。这里只分析 SpringBoot 2.x 的处理过程。这里直接贴一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters,
SpringApplicationRunListeners listeners) {
try {
try {
// exitCode
handleExitCode(context, exception);
if (listeners != null) {
// failed
listeners.failed(context, exception);
}
}
finally {
// 这里也是扩展的口子
reportFailure(exceptionReporters, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}

上述代码片段主要做了以下几件事:

  • handleExitCode: 这里会拿到异常的 exitCode,随后发布一个 ExitCodeEvent 事件,最后交由 SpringBootExceptionHandler 处理。
  • SpringApplicationRunListeners#failed: 循环遍历调用所有 SpringApplicationRunListener 的 failed 方法
  • reportFailure:用户可以自定义扩展 SpringBootExceptionReporter 接口来实现定制化的异常上报逻辑

在 SpringApplicationRunListeners#failed 中,业务产生的异常将直接被抛出,而不会影响异常处理的主流程。

总结

至此,SpringBoot 启动的主流程已经全部分析完成了。从扩展和扩展时机的角度来看,整个过程中,SpringBoot 提供了非常多的扩展口子,让用户可以在容器启动的各个阶段(无论是启动,环境准备,容器刷新等等)做一些定制化的操作。用户可以利用这些扩展接口来修改 bean 、修改环境变量,给用户极大的空间。

原文作者:GuoLei Song

原文链接:http://www.glmapper.com/2019/12/07/springboot-series-started/

发表日期:December 7th 2019, 3:04:28 pm

更新日期:April 5th 2020, 7:44:34 pm

版权声明:转载请注明出处

CATALOG
  1. 1. 从一份配置文件开始说起
  2. 2. 启动入口
  3. 3. SpringApplication 实例的构建
  4. 4. run 方法主流程
    1. 4.1. prepareEnvironment 的处理过程
    2. 4.2. prepareContext 的处理过程
    3. 4.3. refreshContext 的处理过程
      1. 4.3.1. BeanFactoryPostProcessor 处理
      2. 4.3.2. registerBeanPostProcessors 的处理过程
      3. 4.3.3. LifecycleProcessor 的处理过程
    4. 4.4. 异常处理逻辑
  5. 5. 总结