二十三张图带你从源码分析Spring Boot 启动流程

语言: CN / TW / HK

持续原创输出,点击上方蓝字关注我

目录

  • 前言

  • 源码版本

  • 从哪入手?

  • 源码如何切分?

  • 如何创建SpringApplication?

    • 设置应用类型

    • 设置初始化器(Initializer)

    • 设置监听器(Listener)

    • 设置监听器(Listener)

  • 执行run()方法

    • 获取、启动运行过程监听器

    • 环境构建

    • 创建IOC容器

    • IOC容器的前置处理

    • 刷新容器

    • IOC容器的后置处理

    • 发出结束执行的事件

    • 执行Runners

    • 总结

  • 总结

前言

Spring Boot 专栏已经写了五十多天了,前面二十章从基础应用到高级整合避重就轻介绍的都是工作、面试中常见的知识点。

今天开始底层源码介绍的阶段,相对内容比较深一点,作者也尽可能介绍的通俗易懂,层次分明一点。相信读过我写的 Mybatis 专栏的文章都知道,只要跟着作者的步骤,方法一步步研究,其实源码并不难。

这篇文章花了四天时间精雕细琢,力求介绍的通俗易懂,毕竟源码相对难度更高些,希望通过作者拆分讲解能够帮助到读者。

如果没读过作者的前二十篇文章,点击前往

源码版本

作者 Spring Boot 是基于 2.4.0 。每个版本有些变化,读者尽量和我保持一致,以防源码有些出入。

从哪入手?

相信很多人尝试读过 Spring Boot 的源码,但是始终没有找到合适的方法。那是因为你对 Spring Boot 的各个组件、机制不是很了解,研究起来就像大海捞针。

至于从哪入手不是很简单的问题吗,当然主启动类了,即是标注着 @SpringBootApplication 注解并且有着 main() 方法的类,如下一段代码:

@SpringBootApplication
public class AnnotationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AnnotationDemoApplication.class, args);
}
}

话不多说, DEBUG 伺候,别怕,搞它........

源码如何切分?

SpringApplication 中的静态 run() 方法并不是一步完成的,最终执行的源码如下:

//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

很显然分为两个步骤,分别是创建 SpringApplication 和执行 run() 方法,下面将分为这两个部分介绍。

如何创建SpringApplication?

创建即是 new 对象了, DEBUG 跟进代码,最终执行的 SpringApplication 构造方法如下图:

如上图中标注的注释,创建过程重用的其实分为 这三个阶段,下面将会一一介绍每个阶段做了什么事。

设置应用类型

这个过程非常重要,直接决定了项目的类型,应用类型分为三种,都在 WebApplicationType 这个枚举类中,如下:

  1. NONE
    web容器
    Tomcat
    
  2. SERVLET
    servlet
    servlet
    Tomcat
    
  3. REACTIVE
    reactive
    reactive
    

判断的依据很简单,就是加载对应的类,比如加载了 DispatcherServlet 等则会判断是 Servlet 的web程序。源码如下:

static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

这里我引入了 spring-boot-starter-web ,肯定是 Servlet 的web程序。

设置初始化器(Initializer)

初始化器 ApplicationContextInitializer 是个好东西,用于 IOC 容器刷新之前初始化一些组件,比如 ServletContextApplicationContextInitializer

那么如何获取初始化器呢?跟着上图中的代码进入,在 SpringApplication 中的如下图中的方法:

相对重要的就是第一步获取初始化器的名称了,这个肯定是 全类名 了,详细源码肯定在 loadFactoryNames() 方法中了,跟着源码进入,最终调用的是 #SpringFactoriesLoader.loadSpringFactories() 方法。

loadSpringFactories() 方法就不再详细解释了,其实就是从类路径 META-INF/spring.factories 中加载 ApplicationContextInitializer 的值。

spring-boot-autoconfigurespring.factories 文件中的值如下图:

上图中的只是一部分初始化器,因为 spring.factories 文件不止一个。

下图中是我的 demo 中注入的初始化器,现实项目中并不止这些。

这也告诉我们自定义一个 ApplicationContextInitializer 只需要实现接口,在 spring.factories 文件中设置即可。

设置监听器(Listener)

监听器( ApplicationListener )这个概念在 Spring 中就已经存在,主要用于监听特定的事件( ApplicationEvent ),比如IOC容器刷新、容器关闭等。

Spring Boot 扩展了 ApplicationEvent 构建了 SpringApplicationEvent 这个抽象类,主要用于 Spring Boot 启动过程中触发的事件,比如程序启动中、程序启动完成等。如下图:

监听器如何获取?从源码中知道其实和初始化器( ApplicationContextInitializer )执行的是同一个方法,同样是从 META-INF/spring.factories 文件中获取。

spring-boot-autoconfigurespring.factories 文件中的值如下图:

spring.factories 文件不止一个,同样监听器也不止以上这些。

作者 demo 中注入的一些监听器如下图:

总结

SpringApplication 的构建都是为了 run() 方法启动做铺垫,构造方法中总共就有几行代码,最重要的部分就是设置应用类型、设置初始化器、设置监听器。

「注意」:初始化器和这里的监听器都要放置在 spring.factories 文件中才能在这一步骤加载,否则不会生效,因此此时 IOC容器 还未创建,即使将其注入到 IOC容器 中也是不会生效的。

作者简单的画了张执行流程图,仅供参考,如下:

执行run()方法

上面分析了 SpringApplication 的构建过程,一切都做好了铺垫,现在到了启动的过程了。

作者根据源码将启动过程分为了 「8步」 ,下面将会一一介绍。

1. 获取、启动运行过程监听器

SpringApplicationRunListener 这个监听器和 ApplicationListener 不同,它是用来监听应用程序启动过程的,接口的各个方法含义如下:

public interface SpringApplicationRunListener {

// 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}

如何获取运行监听器?

SpringApplication#run() 方法中,源码如下:

//从spring.factories中获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);

跟进 getRunListeners() 方法,其实还是调用了 loadFactoryNames() 方法从 spring.factories 文件中获取值,如下:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

最终注入的是 EventPublishingRunListener 这个实现类,创建实例过程肯定是通过反射了,因此我们看看它的构造方法,如下图:

这个运行监听器内部有一个事件广播器( SimpleApplicationEventMulticaster ),主要用来广播特定的事件( SpringApplicationEvent )来触发特定的监听器 ApplicationListener

EventPublishingRunListener 中的每个方法用来触发 SpringApplicationEvent 中的不同子类。

如何启动运行监听器?

SpringApplication#run() 方法中,源码如下:

//执行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);

执行 SpringApplicationRunListenersstarting() 方法,跟进去其实很简单,遍历执行上面获取的运行监听器,这里只有一个 EventPublishingRunListener 。因此执行的是它的 starting() 方法,源码如下图:

上述源码中逻辑很简单,其实只是执行了 multicastEvent() 方法,广播了 ApplicationStartingEvent 事件。至于 multicastEvent() 内部方法感兴趣的可以看看,其实就是遍历 ApplicationListener 的实现类,找到监听 ApplicationStartingEvent 这个事件的监听器,执行 onApplicationEvent() 方法。

总结

这一步其实就是广播了 ApplicationStartingEvent 事件来触发监听这个事件的 ApplicationListener

因此如果自定义了 ApplicationListener 并且监听了 ApplicationStartingEvent (应用程序开始启动)事件,则这个监听器将会被触发。

2. 环境构建

这一步主要用于加载系统配置以及用户的自定义配置( application.properties ),源码如下,在 run() 方法中:

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

prepareEnvironment 方法内部广播了 ApplicationEnvironmentPreparedEvent 事件,源码如下图:

环境构建这一步加载了系统环境配置、用户自定义配置并且广播了 ApplicationEnvironmentPreparedEvent 事件,触发监听器。

3. 创建IOC容器

源码在 run() 方法中,如下:

context = createApplicationContext();

跟进代码,真正执行的是 ApplicationContextFactory 方法,如下图:

根据 webApplicationType 决定创建的类型,很显然,我这里的是 servlet ,因此创建的是 AnnotationConfigServletWebServerApplicationContext

这一步仅仅是创建了 IOC容器 ,未有其他操作。

4. IOC容器的前置处理

这一步真是精华了,在刷新容器之前做准备,其中有一个非常关键的操作:将启动类注入容器,为后续的自动化配置奠定基础。源码如下:

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

prepareContext() 源码解析如下图,内容还是挺多的:

从上图可以看出步骤很多,下面将会详细介绍几个重点的内容。

调用初始化器

SpringApplication 构建过程中设置的初始化器,从 spring.factories 取值的。执行的流程很简单,遍历执行,源码如下图:

将自定义的 ApplicationContextInitializer 放在 META-INF/spring.factories 中,在此时也是会被调用。

加载启动类,注入容器

这一步是将主启动类加载到 IOC容器 中,作为后续自动配置的入口。

SpringApplication 构建过程中将主启动类放置在 primarySources 这个集合中,此时的 getAllSources() 即是从其中取值,如下图:

这里取出的就是主启动类,当然你的项目中可能不止一个,接下来就是将其加载到IOC容器中了,源码如下:

load(context, sources.toArray(new Object[0]));

跟着代码进去,其实主要逻辑都在 BeanDefinitionLoader.load() 方法,如下图:

将主启动类加载到 beanDefinitionMap ,后续该启动类将作为开启自动配置化的入口,后续章节详细介绍。

两次广播事件

这一步涉及到了两次事件广播,分别是 ApplicationContextInitializedEventApplicationPreparedEvent ,对应的源码如下:

listeners.contextPrepared(context);
load(context, sources.toArray(new Object[0]));

5. 刷新容器

刷新容器完全是 Spring 的功能了,比如初始化资源,初始化上下文广播器等,这个就不再详细介绍,有兴趣可以看看 Spring 的源码。

protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//调用创建的容器applicationContext中的refresh()方法
((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/**
* 刷新上下文环境
*/

prepareRefresh();

/**
* 初始化BeanFactory,解析XML,相当于之前的XmlBeanFactory的操作,
*/

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

/**
* 为上下文准备BeanFactory,即对BeanFactory的各种功能进行填充,如常用的注解@Autowired @Qualifier
* 添加ApplicationContextAwareProcessor处理器
* 在依赖注入忽略实现*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
* 注册依赖,如一个bean的属性中含有ApplicationEventPublisher(beanFactory),则会将beanFactory的实例注入进去
*/

prepareBeanFactory(beanFactory);

try {
/**
* 提供子类覆盖的额外处理,即子类处理自定义的BeanFactoryPostProcess
*/

postProcessBeanFactory(beanFactory);

/**
* 激活各种BeanFactory处理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
* 执行对应的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法
*/

invokeBeanFactoryPostProcessors(beanFactory);

/**
* 注册拦截Bean创建的Bean处理器,即注册BeanPostProcessor,不是BeanFactoryPostProcessor,注意两者的区别
* 注意,这里仅仅是注册,并不会执行对应的方法,将在bean的实例化时执行对应的方法
*/

registerBeanPostProcessors(beanFactory);

/**
* 初始化上下文中的资源文件,如国际化文件的处理等
*/

initMessageSource();

/**
* 初始化上下文事件广播器,并放入applicatioEventMulticaster,如ApplicationEventPublisher
*/

initApplicationEventMulticaster();

/**
* 给子类扩展初始化其他Bean
*/

onRefresh();

/**
* 在所有bean中查找listener bean,然后注册到广播器中
*/

registerListeners();

/**
* 设置转换器
* 注册一个默认的属性值解析器
* 冻结所有的bean定义,说明注册的bean定义将不能被修改或进一步的处理
* 初始化剩余的非惰性的bean,即初始化非延迟加载的bean
*/

finishBeanFactoryInitialization(beanFactory);

/**
* 通过spring的事件发布机制发布ContextRefreshedEvent事件,以保证对应的监听器做进一步的处理
* 即对那种在spring启动后需要处理的一些类,这些类实现了ApplicationListener<ContextRefreshedEvent>,
* 这里就是要触发这些类的执行(执行onApplicationEvent方法)
* 另外,spring的内置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
* 完成初始化,通知生命周期处理器lifeCycleProcessor刷新过程,同时发出ContextRefreshEvent通知其他人
*/

finishRefresh();
}

finally {

resetCommonCaches();
}
}
}

6. IOC容器的后置处理

一个扩展方法,源码如下:

afterRefresh(context, applicationArguments);

默认为空,如果有自定义需求可以重写,比如打印一些启动结束日志等。

7. 发出结束执行的事件

同样是 EventPublishingRunListener 这个监听器,广播 ApplicationStartedEvent 事件。

但是这里广播事件和前几次不同,并不是广播给 SpringApplication 中的监听器(在构建过程中从 spring.factories 文件获取的监听器)。因此在 IOC容器 中注入的监听器(使用 @Component 等方式注入的)也能够生效。前面几个事件只有在 spring.factories 文件中设置的监听器才会生效。

跟着代码进入,可以看到 started() 方法源码如下:

这里并没有用事件广播器 SimpleApplicationEventMulticaster 广播事件,而是使用 ConfigurableApplicationContext 直接在 IOC容器 中发布事件。

8. 执行Runners

Spring Boot 提供了两种 Runner 让我们定制一些额外的操作,分别是 CommandLineRunnerApplicationRunner ,关于这两个的区别,后面文章详细介绍。

调用的源码如下:

callRunners(context, applicationArguments);

跟进代码,其实真正执行的是如下方法:

逻辑很简单,从 IOC容器 中获取,遍历调用。

总结

Spring Boot 启动流程相对简单些,作者将其细分了以上八个步骤,希望能够帮助读者理解,流程图如下:

总结

Spring Boot 启动流程就介绍到这里了,需要重点理解 run() 方法执行的八个步骤以及事件、初始化器、监听器等组件的执行时间点。

作者每一篇文章都很用心,这篇源码解析花了三天时间精雕细琢,力求讲解的通俗易懂,希望能够帮助到你。

另外作者的第一本 PDF 书籍已经整理好了,由浅入深的详细介绍了Mybatis基础以及底层源码,有需要的朋友公众号回复关键词 「Mybatis进阶」 即可获取,目录如下: