Spring Boot 启动时做了什么?

启动从main方法调用静态方法SpringApplication.run(MainClass.class, args)开始,最终会返回一个应用上下文ConfigurableApplicationContext。
通常MainClass,即应用的主类会带@SpringBootApplication注解

TIP

Spring官方建议一个应用中只能有一个@SpringBootApplication or @EnableAutoConfiguration注解

初始化SpringApplication对象

构造方法中设置了资源加载器ResourceLoader,主资源primarySource,web应用类型,初始化ApplicationContextInitializer和ApplicationListener,应用的主类(main方法所在的类,默认情况下应该和primarySource是同一个类)。

应用类型分为:SERVLET应用、REACTIVE应用、非WEB应用。

在ResourceLoader中可以指定应用加载的资源和类加载器。

其中initializer和listener都会从META-INF/spring.factories中加载实例,大部分都定义在spring-boot-x.x.x.RELEASE.jar和spring-boot-autoconfigure-x.x.x.RELEASE.jar文件中。

TIP

spring.factories文件加载的缓存保存在SpringFactoriesLoader静态字段cache中SpringBoot2,cache是一个map,每一个类加载器对应cache中的一个key。

初始化完成后则调用SpringApplication对象的run方法(不是静态的run方法),run方法完成了整个应用的初始化。SpringApplication提供了一些set接口,可以在调用run方法之前对其进行定制。

spring应用生命周期

下面就正式进入run方法。SpringApplicationRunListener接口定义了spring应用生命周期中的几个状态,下面就根据这些状态完整介绍一下spring应用生命周期,在每个状态下都描述了进入该状态需要完成的操作,进入相应状态后会调用SpringApplicationRunListener接口的对应方法。

starting

进入starting状态前,最主要的操作是实例化SpringApplicationRunListener。SpringApplicationRunListener也是从META-INF/spring.factories中加载实例。

SpringApplicationRunListener接口是一个spring全生命周期的钩子,会在spring应用的各个生命周期被调用。

默认应该至少会有一个EventPublishingRunListener,定义在spring-boot-x.x.x.RELEASE.jar中。EventPublishingRunListener用于发布Spring事件SpringApplicationEvent,应用准备启动时就会发布一个ApplicationStartingEvent事件。

TIP

ApplicationListener和SpringApplicationRunListener接口的区别。
ApplicationListener接口只会监听自己所关心的spring事件并进行处理,一般情况下一个ApplicationListener只监听一个事件,可以自由新增事件或新增监听器,另ApplicationListener可用于spring和springboot;
SpringApplicationRunListener更多是作为springboot生命周期的钩子,在springboot生命周期的特定位置被调用,不太容易扩展(需要改底层源码),其中EventPublishingRunListener就是在各个生命周期节点发布事件,而ApplicationListener实现类只要关注了事件就会被触发。

WARNING

SpringApplicationRunListener抛异常会导致程序运行终止

environment prepared

解析命令行参数,存到ApplicationArguments接口的实现类DefaultApplicationArguments中,以"--"开头的参数会被解析为option参数,否则解析为非option参数。

初始化Environment,Environment中的getPropertySources()保存了所有的属性源,命令行参数解析后也被加入到属性源中,这样应用的命令行参数就可以做为Spring的properties来使用,也可以在Environment中读取到。

TIP

属性源是有优先级的,命令行属性源被设为最高优先级,因此在命令行中设置properties可以覆盖其他properties。查看完整的属性源优先级

从属性源中查找spring.profiles.active属性,设置为Environment的激活profile,另外,如果有在SpringApplication对象上添加额外的profile(通过调用setAdditionalProfiles),这些profile会合并到Environment中。

TIP

这里涉及的configureEnvironment/configurePropertySources/configureProfiles这几个方法都可以被重写,以便定制Environment。

把Environment绑定到SpringApplication,以便后续使用。

环境准备完成后,由EventPublishingRunListener广播了一个ApplicationEnvironmentPreparedEvent事件,订阅了此事件的监听器有:

  • ConfigFileApplicationListener,在这里会加载application.properties文件
  • LoggingApplicationListener,这里会初始化应用的日志系统,参阅日志系统加载

context prepared

打印banner,属性banner.location=classpath:banner.txt定义了banner的内容,可以添加banner.txt文件来修改banner。

创建应用上下文实例,根据应用类型来决定上下文的类型,创建实例。可以通过SpringApplication的setApplicationContextClass来自定义应用上下文类型,如果这么做应用类型就会根据上下文类型来推断。

TIP

三种应用类型对应的上下文类型分别是:

  • SERVLET -> AnnotationConfigServletWebServerApplicationContext
  • REACTIVE -> AnnotationConfigReactiveWebServerApplicationContext
  • 非WEB类型 -> AnnotationConfigApplicationContext

TIP

如果引入了SpringBoot Actuator特性,Spring上下文和上下文中的bean可以通过beans端点查看

从META-INF/spring.factories中实例化SpringBootExceptionReporter,用于报告应用启动时产生的异常。

后处理应用上下文postProcessApplicationContext(可重写)。这里很重要的是把SpringApplication中的resourceLoader和classloader设置到上下文中,如果构造SpringApplication对象时指定了resourceLoader则需要关注。

调用所有ApplicationContextInitializer实例的initialize方法进行初始化操作。

context loaded

打印应用的启动日志和激活的profile日志。

使用context.getBeanFactory().registerSingleton()方法注册了几个单例bean:springApplicationArguments、springBootBanner。

从primarySources和sources中加载BeanDefinition,一般至少有一个primarySource(springboot的主类),这里的source可以是配置类、package或xml、groovy。
加载是通过BeanDefinitionLoader这个类的实例来完成的,BeanDefinitionLoader提供了几个重载的load方法,可分别从各种source中加载BeanDefinition。

  • 如果资源是带注解的Class将委托给AnnotatedBeanDefinitionReader来加载
  • 如果资源是xml文件将委托给XmlBeanDefinitionReader来加载
  • 如果资源是groovy(SpringBoot支持)将委托给GroovyBeanDefinitionReader来加载
  • 如果资源是Package将委托给ClassPathBeanDefinitionScanner进行包路径扫描

加载完成后在beanFactory中就可以使用BeanDefinition。

started

调用AbstractApplicationContext的refresh()进行上下文刷新。

上下文刷新最主要的操作是把BeanDefinition转化为Bean对象实例。

自动化配置也在这一步完成,在refresh()中会调用invokeBeanFactoryPostProcessors(beanFactory),进而通过ConfigurationClassPostProcessor完成自动化配置,详见自动化配置

如果设置了registerShutdownHook=true,这一步还会注册应用程序关闭钩子,调用AbstractApplicationContext的doClose()方法,这里会发布ContextClosedEvent事件,当然还预留了一个onClose()方法交给子类去实现。

afterRefresh(),这个方法的默认实现是空的,应该是spring在这留的一个扩展点

running

运行所有的CommandLineRunner或ApplicationRunner bean

TIP

只要Spring上下文中包含CommandLineRunner或ApplicationRunner类型的bean,都会在应用启动时被运行,其区别是:CommandLineRunner只能获取到原始的字符串命令行参数,ApplicationRunner则可以获取到解析后的选项(类似于--option1=a --option2=b)

WARNING

Runner抛出异常会导致程序运行终止

failed

以上除了进入starting状态之前做的一些初始化操作之外,其他任何一处发生异常都会使应用进入failed状态,并进行应用退出的处理:处理退出码、报告异常。

日志系统加载

SpringBoot提供了对日志系统的抽象,顶级抽象类是org.springframework.boot.logging.LoggingSystem,下面有几个实现类是LogbackLoggingSystem、Log4J2LoggingSystem分别表示logback日志系统和log4j2日志系统。

日志系统的加载步骤:

  1. 监听到ApplicationStartingEvent事件,从当前classpath中查找特定的类,如果找到ch.qos.logback.core.Appender则创建LogbackLoggingSystem,如果找到org.apache.logging.log4j.core.impl.Log4jContextFactory则创建Log4J2LoggingSystem
  2. 调用日志系统的beforeInitialize()进行预初始化
  3. 监听到ApplicationEnvironmentPreparedEvent事件,对日志系统进行初始化
  4. 创建日志文件对象,日志文件的位置在${logging.file.path}+${logging.file.path}
  5. 把日志相关的属性设置到SystemProperty中
  6. 调用日志系统的initialize()进行初始化

属性源优先级

所有属性源中的属性都可以在代码中用@Value来直接使用,或直接注入Environment对象,使用Environment对象来动态获取,或可以绑定到@ConfigurationProperties上。

TIP

关于@ConfigurationProperties的使用,可以参照这篇文章

在不同的属性源中如果有相同的属性,将会按优先级顺序加载,优先级如下:

  1. 家目录下的~/.spring-boot-devtools.properties文件(如果开启了devtools)
  2. 用@TestPropertySource标记的类
  3. 测试目录下的properties
  4. 命令行参数
  5. SPRING_APPLICATION_JSON属性,可以用--spring.application.json='{"name":"test"}'方式在命令行设置一串json属性
  6. ServletConfig初始化参数
  7. ServletContext初始化参数
  8. 来自于java:comp/env的JNDI属性
  9. Java系统属性,即System.getProperties()
  10. 操作系统环境变量
  11. 随机值属性源RandomValuePropertySource,会提供一些random.*属性
  12. jar包外带profile的属性文件,即application-{profile}.properties/yml
  13. jar包内带profile的属性文件
  14. jar包外不带profile的属性文件,即application.properties/yml
  15. jar包内不带profile的属性文件
  16. 用@PropertySource和@Configuration标记的类
  17. 用SpringApplication.setDefaultProperties设置的默认属性

TIP

第12和14条jar包外的属性文件是指在jar包当前目录下的./config目录或./目录

Last Updated 2024/11/25 02:50:15