优秀的编程知识分享平台

网站首页 > 技术文章 正文

你不会还不知道SpringBoot启动流程吧?赶紧来看看!

nanyue 2025-02-28 16:51:57 技术文章 12 ℃

当我们启动SpringBoot项目时,分为两步:创建SpringApplication类和执行run方法。

以下文章的阅读顺序即为方法的执行顺序

创建SpringApplication类

  • 确定webApplicationType类型(创建哪种Spring容器)

关于这部分的作用,我曾经在《SpringBoot启动web项目时是如何选择上下文容器的?》一文中做了详细的解释。目的就是为了在创建容器的时候,根据webApplicationType去创建指定的容器

  • 根据SPI机制获取ApplicationContextInitializer接口实现类

关于的SPI机制的介绍,我在《Spring自动装配原理代码跟踪详解及其实践》一文中做了详细的解释。我们可以通过实现
ApplicationContextInitializer接口,重写initialize方法。该方法的调用时机是在Spring容器创建之后(prepareContext阶段)。同时我们需要创建META-INF/spring.factories文件,key=
org.springframework.context.ApplicationContextInitializer,对应的value=对应实现类的权限定类名。

  • 根据SPI机制获取ApplicationListener接口实现类


ApplicationContextInitializer同理,我们可以通过实现ApplicationListener接口,重写onApplicationEvent方法,然后创建META-INF/spring.factories文件,key=
org.springframework.context.ApplicationListener,对应的value=对应实现类的权限定类名。

该接口的调用时机在Spring容器创建之后(prepareContext阶段),通过
SpringApplicationRunListeners的contextLoaded方法进行调用(实际是
EventPublishingRunListener,下面会讲),调用时机比
ApplicationContextInitializer晚一点点。

  • 获取main方法所在的主类

个人观察到的这个东西更像是在打印日志的时候才会使用到,或者在打印banner信息的时候。

调用run方法

  • 获取根据SPI机制获取SpringApplicationRunListener接口实现类

在调用run方法中,这是第一个被执行的方法,通过SPI机制获取到所有的
SpringApplicationRunListener接口的实现类,并将集合信息放到
SpringApplicationRunListeners中的listeners集合中。此刻,用到了一种设计模式:
观察者设计模式也叫发布订阅模式。

在之后Spring启动的各个阶段中,我们均可以看到
SpringApplicationRunListeners类的对应方法的执行。


SpringApplicationRunListeners类中对应的方法:starting()、environmentPrepared()、contextPrepared()、contextLoaded()、started()、running()、failed()。实际上调用的就是
SpringApplicationRunListener中对应的方法。
SpringApplicationRunListeners类中的方法将在Spring容器执行过程中的不同阶段被依次执行,我们可以根据被执行的时机来自定义一些我们要做的事情。


EventPublishingRunListener类实现了
SpringApplicationRunListener接口,因此在程序启动的各个阶段均能看到它对应的方法被调用,实际上我们通过SPI机制获取的ApplicationListener接口实现类的调用也是通过它来实现的,在接下来的讲解中我们将会看到。

  • 调用SpringApplicationRunListeners的starting方法

实际执行的是集合中的
SpringApplicationRunListener类的starting方法,这个方法没有任何参数,可以做一些
SpringApplicationRunListener类的初始化工作。例如我们的
EventPublishingRunListener类,在starting阶段,它发布了ApplicationStartedEvent事件

  • 获取spring环境

读取外部化配置环境信息是在prepareEnvironment方法执行时处理的,创建ConfigurableEnvironment环境信息,配置环境变量,包含系统属性和用户配置的属性。

外部化配置通常由application.yml文件(或者application.properties文件)提供,在application.yml文件中添加配置项也是最常用的外部化配置方式。下面是较为常用的外部化配置方式的优先级(由上到下优先级逐渐降低)。

1、命令行参数,即Command line arguments;

2、JAVA系统属性,即Java System properties(System#getProperties);

3、操作系统环境变量,即OS environment variables;

4、配置数据文件(例如application.yml文件),即Config data(such as application.properties files)jar包外指定了profile的配置数据文件:application-{profile}.ymljar包外的配置数据文件:application.ymljar包内指定了profile的配置数据文件:application-{profile}.ymljar包内的配置数据文件:application.yml

5、作用在由@Configuration注解修饰的类上的@PropertySource注解,即@PropertySourceannotations on your @Configuration classes;

6、默认属性,即Default properties(specified by setting SpringApplication#setDefaultProperties)。

ConfigurableEnvironment的实际类型为
StandardServletEnvironment,这是和Springboot的应用程序类型挂钩。
StandardServletEnvironment内部持有一个MutablePropertySources对象,该对象持有一个PropertySource的集合,Springboot加载的每一种外部化配置都会最终被解析为一个PropertySource的实现类并存放在MutablePropertySources的PropertySource集合中,PropertySource就是每一种外部化配置源在Springboot中的体现,其提供了对外部化配置的各种操作。

说一下ConfigurableEnvironment创建过程:

在SpringApplication#prepareEnvironment方法中,首先会调用getOrCreateEnvironment() 方法创建ConfigurableEnvironment对象,创建出来的ConfigurableEnvironment的实际类型会根据SpringApplication初始化时推断出来的WEB应用程序类型而定(webApplicationType),如果WEB应用程序类型为SERVLET,则创建出来的ConfigurableEnvironment实际类型为
StandardServletEnvironment,并且在初始化
StandardServletEnvironment时还会一并将JAVA系统属性和操作系统环境变量这两个外部化配置加载到
StandardServletEnvironment中。

  • 调用SpringApplicationRunListener的environmentPrepared方法

实际执行的是集合中的
SpringApplicationRunListener类的environmentPrepared方法,这个方法会将ConfigurableEnvironment参数传入,因此可以对ConfigurableEnvironment对象做一些额外的工作。例如我们的yml读取是在此阶段实现的,通过
ConfigFileApplicationListener类(
2.4.0版本以前),之后是
EnvironmentPostProcessorApplicationListener。然后

SpringApplicationRunListener发布environmentPrepared方法,紧接着调用
ConfigFileApplicationListener的onApplicationEvent进行读取环境信息。

而在
EnvironmentPostProcessorApplicationListener中实际上也用到了SPI机制去获取
EnvironmentPostProcessor后置处理器。然后通过EnvironmentPostProcessor去读取各个环境。

  • 获取banner信息(不重要)

这个其实没啥意思,有兴趣的可以看:《Banner-个性化定制控制台打印信息》


  • 根据webApplicationType创建Spring容器ApplicationContext

在实例化SpringApplication阶段我们已经确定了webApplicationType的类型,这里我们便需要用到它:

这里我们可以看到,会根据webApplicationType的类型的类型去创建对应的上下文容器

当我们创建一个web项目时,这里会创建
AnnotationConfigServletWebServerApplicationContext容器,具体怎么就是个web项目了,可以看webApplicationType是怎么确定的。

接下来我们看一下
AnnotationConfigServletWebServerApplicationContext实例化的时候做了什么事情

  • AnnotationConfigServletWebServerApplicationContext上下文容器实例化

在了解此章节前,可能需要你知道什么是BeanDefinition,详情请参考:

《什么是beanDefinition》


《关于BeanDefinition,你想知道的都在这里》


这里是
AnnotationConfigServletWebServerApplicationContext的无参构造方法,看到一共创建了两个对象
AnnotatedBeanDefinitionReader和
ClassPathBeanDefinitionScanner。


AnnotatedBeanDefinitionReader的作用除了负责注册beanDefinition以外,在创建的时候spring也默认向其中注册了最为重要的几个类

内部注册了最为重要的
ConfigurationClassPostProcessor类,当然还有其他类。
ConfigurationClassPostProcessor中负责大部分常用注解的解析过程,经过
ConfigurationClassPostProcessor的处理后,基本上我们项目中的类都会被注册beanDefinition


ClassPathBeanDefinitionScanner则是负责扫描类,创建beanDefinition,具体我们可以到解析@ComponentScan时看

  • 通过SPI机制获取SpringBootExceptionReporter自定义打印异常信息

其实这个我并没有自己实践过,仅仅知道Spring启动过程中的异常是可以自定义的

当发生异常后try-catch后进行处理。

  • prepareContext-预处理上下文容器,绑定环境信息

当上下文容器被初始化后,紧接着便需要向容器中去绑定一些信息,例如环境信息、bean名字处理器等。此后
ApplicationContextInitializer的initialize方法便会执行,这里不再赘述,最开始实例化的时候已经说了。

  • prepareContext-调用SpringApplicationRunListener的contextPrepared方法

我们已经说过,
SpringApplicationRunListener中的方法会贯穿Spring启动的一生,此时在容器创建完毕后,可以对容器做一些自定义处理,contextPrepared方法会传入
ConfigurableApplicationContext,即刚实例化的容器上下文。

  • prepareContext-将启动类注册到spring容器中,注册为BeanDefinition

在prepareContext中,最重要的一步大概就是将我们main方法所在的类注册为BeanDefinition了,因为启动类是我们扫描整个项目的开端。

判断当前主类上有Component注解,然后向注册器发起注册。

经过此后,我们项目中的第一个类便被注册为了BeanDefinition!

  • prepareContext-调用SpringApplicationRunListener的contextLoaded方法

当主类被注册后,紧接着
SpringApplicationRunListener的contextLoaded方法便开始执行,同时依然传入我们的上下文容器,但是此时容器中已经有了我们的主类等信息。

  • refreshContext-prepareRefresh刷新上下文容器之预刷新

经过上面的那些方法后我们的上下文已经准备好了,接下来refresh方法便开始扫描我们的所有类开始注册BeanDefinition并实例化BeanDefinition为bean!,同时又处理了除此之外的重要信息,我们依依来看。

我们先说prepareRefresh方法,它是refresh方法中第一个开始执行的,感觉里面做的事情也不是很重要,我们就不赘述了,给个截图吧

  • refreshContext-prepareBeanFactory准备我们的bean工厂信息

我们的BeanFactory就在我们的上下文中,它作为一个成员变量存在于上下文中。如果说上下文容器是我们的核心的话,那么BeanFactory便是核心中的核心。

在此阶段,我们向
ignoredDependencyInterfaces集合中添加了参数,该set集合的作用是忽略集合中的类通过set进行注入:

例如ApplicationContextAware接口,此接口的作用可以看:《什么是spring-Aware?》

当我们实现ApplicationContextAware接口接口后,重写setApplicationContext方法,便可以获取对应的信息。但是如果我们实现了该Aware接口同时对ApplicationContext属性进行set方法注入时,此时set注入便会失效。

同时向resolvableDependencies的map中添加参数,该map的作用是当我们使用接口注入时,实际注入的类就是我们指定的value类:

举例:当我们注入BeanFactory时,由于BeanFactory是一个接口,对应有很多实现类,那么Spring应该选择哪一个进行注入呢?对应的注入值就是此map的value值。

此外还会向容器中注册一些额外的信息。

  • refreshContext-postProcessBeanFactory后置处理beanFactory

可以处理一些额外的信息,例如扫描bean等。


  • refreshContext-invokeBeanFactoryPostProcessors开始执行BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor

此方法在Spring启动过程中起到了至关重要的作用!我们的
ConfigurationClassPostProcessor就是此时执行的!

在此方法中,我们所有的BeanFactoryPostProcessor接口实现类和所有的
BeanDefinitionRegistryPostProcessor接口实现类将会被执行。这里是Spring扩展的一部分

首先执行的是通过监听等方式设置的
BeanDefinitionRegistryPostProcessor接口实现类的
postProcessBeanDefinitionRegistry后置处理方法。

然后是Spring内部注册的
BeanDefinitionRegistryPostProcessor接口实现类的
postProcessBeanDefinitionRegistry后置处理方法,其中
ConfigurationClassPostProcessor后置处理器的
postProcessBeanDefinitionRegistry方法就是在此时执行,该后置处理方法具有巨大的能量,经过此方法处理,我们项目中的绝大部分类都会被注册为BeanDefinition并存放到BeanFactory对应的容器中。由于我们是说启动流程,因此这里的代码实现就不再讲述了。

由于上面会将我们定义的
BeanDefinitionRegistryPostProcessor接口实现类扫描并注册为BeanDefinition,接下来就会通过getBean的方式获取对应的bean对象(没有就会创建),然后执行我们自定义的
BeanDefinitionRegistryPostProcessor接口实现类中的
postProcessBeanDefinitionRegistry后置处理方法。



BeanDefinitionRegistryPostProcessor接口调用完后接下来就是BeanFactoryPostProcessor接口实现类被调用。

首先就是通过监听等方式设置的BeanFactoryPostProcessor接口实现类的postProcessBeanFactory后置处理方法,因为
BeanDefinitionRegistryPostProcessor也是继承了BeanFactoryPostProcessor接口,因此此时
BeanDefinitionRegistryPostProcessor中的postProcessBeanFactory也会被执行。

然后就是我们自定义的BeanFactoryPostProcessor接口实现类的postProcessBeanFactory后置处理方法执行,这里也同理,
BeanDefinitionRegistryPostProcessor中的postProcessBeanFactory也会被执行。


  • refreshContext-注册BeanPostProcessor后置处理器到容器中

该方法的作用是为了从BeanFactory中获取所有的BeanPostProcessor接口实现类,注意此刻所有的BeanPostProcessor还是BeanDefinition,然后通过getBean实例化所有的BeanPostProcessor,并将实例信息注册到beanPostProcessors集合中。

这里也是我们bean生命周期的一部分。

除了注册我们的BeanPostProcessor,这里也创建了几个内部的BeanPostProcessor,例如
ApplicationListenerDetector。


  • refreshContext-初始化MessageSource国际化信息

这里就是创建了messageSource,然后可以支持国际化信息。

可以参考:《这篇文章让你搞懂 SpringMVC的国际化》


  • refreshContext-初始化事件发布器ApplicationEventMulticaster

我们常用的spring的事件发布机制,对应的
ApplicationEventMulticaster发布器就是在这里创建的。它负责发布我们的事件信息。

  • refreshContext-onRefresh处理化额外的bean信息

在这里,如果是web项目的话,会创建我们的tomcat、jetty等服务器信息。

同时HttpServletRequest等bean会被注入进来,请参考:

《为什么HttpServletRequest可以直接注入?》

同时ServletContextInitializer的onStartup方法也会在此时被调用,该接口被RegistrationBean实现用于往ServletContext容器中注册servelt(ServletRegistrationBean)、filter(FilterRegistrationBean)或者Listener。

  • refreshContext-registerListeners注册所有的ApplicationListener到事件发布器

上面我们说了事件发布器已经被注册,这里我们将获取所有的ApplicationListener信息并实例化,然后注册到事件发布器中,用于发布事件。

  • refreshContext-finishBeanFactoryInitialization方法中获取容器中的所有的BeanDefinition并开始实例化为bean对象

这已经是到refreshContext的最后了,经过上面的一系列处理后,我们的bean将会统一在这里被处理,通过beanFactory的preInstantiateSingletons将所有的非懒加载的bean进行创建。

  • refreshContext-finishRefresh完成bean注册后的事件发布

这里是容器正常刷新的最后一个方法,在这里,将会发布容器创建完成事件,如果是web项目,这里也将会启动tomcat等。

  • refreshContext-destroyBeans出现异常后的处理

这里是当容器创建过程中如果出现异常,bean的销毁工作将会在这里被执行。

  • 调用SpringApplicationRunListener的started方法

到此时我们又回到了SpringApplication类中,经过上面的一系列处理后,我们的Spring项目已经基本完成了,这里将调用
SpringApplicationRunListener的started方法。

  • callRunners-调用ApplicationRunner、CommandLineRunner接口

这里也是我们平时常用的接口,例如我们想在项目启动的时候自动加载一些缓存信息等,可以实现ApplicationRunner或者CommandLineRunner接口,重写对应的run方法即可

这俩接口的调用顺序是ApplicationRunner接口在前。

  • SpringBootExceptionReporter自定义异常打印和SpringApplicationRunListener的failed方法

我们上面说了我们可以通过SPI机制来创建我们的
SpringBootExceptionReporter,如果容器执行过程中出现异常,我们可以自定义一些打印信息。将在这里执行。

同时
SpringApplicationRunListener的failed方法将会执行。

  • 调用SpringApplicationRunListener的running方法

如果一切正常,那么这就到了项目启动的最后一步,Spring将调用
SpringApplicationRunListener的running方法,标志着项目正在运行。

这里也可以看到如果running方法异常,就会调用handleRunFailure方法,那么上一步的异常处理对应的信息将会执行。

总结

即便是说了这么多,依然感觉没有说全,Spring在启动过程中蕴藏了很多有意思的东西,依然还需要去探索。了解启动过程可以帮助我们定位问题,同时也可以更好的帮助我们理解Spring,去学习里面的一些编程思想。另外有什么不足之处或者哪里有说错的还请多多包涵~开发这条路还有很远要走,继续前行!

最近发表
标签列表