头秃了,二十三张图带你从源码了解SpringBoot的启动流程~

  • 前言
  • 源码版本
  • 从哪入手?
  • 源码如何切分?
  • 如何创建SpringApplication?
  • 设置应用类型
  • 设置初始化器(Initializer)
  • 设置监听器(Listener)
  • 设置监听器(Listener)
  • 执行run()方法
  • 获取、启动运行过程监听器
  • 环境构建
  • 创建IOC容器
  • IOC容器的前置处理
  • 刷新容器
  • IOC容器的后置处理
  • 发出结束执行的事件
  • 执行Runners
  • 总结
  • 总结

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

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

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

源码版本

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

从哪入手?

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

<br><span class="hljs-keyword">public</span>&#xA0;<span class="hljs-class"><span class="hljs-keyword">class</span>&#xA0;<span class="hljs-title">AnnotationDemoApplication</span>&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">public</span>&#xA0;<span class="hljs-keyword">static</span>&#xA0;<span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">main</span><span class="hljs-params">(String[]&#xA0;args)</span>&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;SpringApplication.run(AnnotationDemoApplication<span class="hljs-class">.<span class="hljs-keyword">class</span>,&#xA0;<span class="hljs-title">args</span>)</span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>}

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

源码如何切分?

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

<br><span class="hljs-function"><span class="hljs-keyword">public</span>&#xA0;<span class="hljs-keyword">static</span>&#xA0;ConfigurableApplicationContext&#xA0;<span class="hljs-title">run</span><span class="hljs-params">(Class<?>[]&#xA0;primarySources,&#xA0;String[]&#xA0;args)</span>&#xA0;</span>{<br>&#xA0;&#xA0;<span class="hljs-keyword">return</span>&#xA0;<span class="hljs-keyword">new</span>&#xA0;SpringApplication(primarySources).run(args);<br>&#xA0;}

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

如何创建SpringApplication?

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

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

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

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

<span class="hljs-function"><span class="hljs-keyword">static</span>&#xA0;WebApplicationType&#xA0;<span class="hljs-title">deduceFromClasspath</span><span class="hljs-params">()</span>&#xA0;</span>{<br>&#xA0;&#xA0;<span class="hljs-keyword">if</span>&#xA0;(ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS,&#xA0;<span class="hljs-keyword">null</span>)&#xA0;&&&#xA0;!ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS,&#xA0;<span class="hljs-keyword">null</span>)<br>&#xA0;&#xA0;&#xA0;&#xA0;&&&#xA0;!ClassUtils.isPresent(JERSEY_INDICATOR_CLASS,&#xA0;<span class="hljs-keyword">null</span>))&#xA0;{<br>&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">return</span>&#xA0;WebApplicationType.REACTIVE;<br>&#xA0;&#xA0;}<br>&#xA0;&#xA0;<span class="hljs-keyword">for</span>&#xA0;(String&#xA0;className&#xA0;:&#xA0;SERVLET_INDICATOR_CLASSES)&#xA0;{<br>&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">if</span>&#xA0;(!ClassUtils.isPresent(className,&#xA0;<span class="hljs-keyword">null</span>))&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">return</span>&#xA0;WebApplicationType.NONE;<br>&#xA0;&#xA0;&#xA0;}<br>&#xA0;&#xA0;}<br>&#xA0;&#xA0;<span class="hljs-keyword">return</span>&#xA0;WebApplicationType.SERVLET;<br>&#xA0;}

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

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

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

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

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

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

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

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

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

监听器( 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&#x5BB9;&#x5668;还未创建,即使将其注入到 IOC&#x5BB9;&#x5668;中也是不会生效的。

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

执行run()方法

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

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

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

<span class="hljs-keyword">public</span>&#xA0;<span class="hljs-class"><span class="hljs-keyword">interface</span>&#xA0;<span class="hljs-title">SpringApplicationRunListener</span>&#xA0;</span>{<br><br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">starting</span><span class="hljs-params">()</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">environmentPrepared</span><span class="hljs-params">(ConfigurableEnvironment&#xA0;environment)</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">contextPrepared</span><span class="hljs-params">(ConfigurableApplicationContext&#xA0;context)</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">contextLoaded</span><span class="hljs-params">(ConfigurableApplicationContext&#xA0;context)</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">started</span><span class="hljs-params">(ConfigurableApplicationContext&#xA0;context)</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">running</span><span class="hljs-params">(ConfigurableApplicationContext&#xA0;context)</span></span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-function"><span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">failed</span><span class="hljs-params">(ConfigurableApplicationContext&#xA0;context,&#xA0;Throwable&#xA0;exception)</span></span>;<br>}

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

<br>SpringApplicationRunListeners&#xA0;listeners&#xA0;=&#xA0;getRunListeners(args);

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

org.springframework.boot.SpringApplicationRunListener=\<br>org.springframework.boot.context.event.EventPublishingRunListener

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

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

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

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

<br>listeners.starting(bootstrapContext,&#xA0;<span class="hljs-keyword">this</span>.mainApplicationClass);

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

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

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

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

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

ConfigurableEnvironment&#xA0;environment&#xA0;=&#xA0;prepareEnvironment(listeners,&#xA0;bootstrapContext,&#xA0;applicationArguments);

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

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

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

context&#xA0;=&#xA0;createApplicationContext();

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

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

这一步仅仅是创建了 IOC&#x5BB9;&#x5668;,未有其他操作。

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

prepareContext(context,&#xA0;environment,&#xA0;listeners,&#xA0;applicationArguments,printedBanner);

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

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

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

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

这一步是将主启动类加载到 IOC&#x5BB9;&#x5668;中,作为后续自动配置的入口。

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

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

load(context,&#xA0;sources.toArray(<span class="hljs-keyword">new</span>&#xA0;Object[<span class="hljs-number">0</span>]));

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

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

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

listeners.contextPrepared(context);<br>load(context,&#xA0;sources.toArray(<span class="hljs-keyword">new</span>&#xA0;Object[<span class="hljs-number">0</span>]));

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

<span class="hljs-function"><span class="hljs-keyword">protected</span>&#xA0;<span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">refresh</span><span class="hljs-params">(ApplicationContext&#xA0;applicationContext)</span>&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;Assert.isInstanceOf(AbstractApplicationContext<span class="hljs-class">.<span class="hljs-keyword">class</span>,&#xA0;<span class="hljs-title">applicationContext</span>)</span>;<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;((AbstractApplicationContext)applicationContext).refresh();<br>}<br><span class="hljs-function"><span class="hljs-keyword">public</span>&#xA0;<span class="hljs-keyword">void</span>&#xA0;<span class="hljs-title">refresh</span><span class="hljs-params">()</span>&#xA0;<span class="hljs-keyword">throws</span>&#xA0;BeansException,&#xA0;IllegalStateException&#xA0;</span>{<br>&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">synchronized</span>&#xA0;(<span class="hljs-keyword">this</span>.startupShutdownMonitor)&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;prepareRefresh();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;ConfigurableListableBeanFactory&#xA0;beanFactory&#xA0;=&#xA0;obtainFreshBeanFactory();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;prepareBeanFactory(beanFactory);<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">try</span>&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;postProcessBeanFactory(beanFactory);<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;invokeBeanFactoryPostProcessors(beanFactory);<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;registerBeanPostProcessors(beanFactory);<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;initMessageSource();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;initApplicationEventMulticaster();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;onRefresh();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;registerListeners();<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;finishBeanFactoryInitialization(beanFactory);<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;finishRefresh();<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;}<br><br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<span class="hljs-keyword">finally</span>&#xA0;{<br>&#xA0;&#xA0;&#xA0;&#xA0;<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;resetCommonCaches();<br>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;}<br>&#xA0;&#xA0;&#xA0;&#xA0;}<br>}

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

afterRefresh(context,&#xA0;applicationArguments);

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

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

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

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

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

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

调用的源码如下:

callRunners(context,&#xA0;applicationArguments);

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

逻辑很简单,从 IOC&#x5BB9;&#x5668;中获取,遍历调用。

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

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

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

Original: https://www.cnblogs.com/Chenjiabing/p/14005575.html
Author: 爱撒谎的男孩
Title: 头秃了,二十三张图带你从源码了解SpringBoot的启动流程~

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/710969/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球