SpringBoot系列之启动流程1-SpringApplication的构造方法
前言
本系列开始研究 SpringBoot 的源码,之前看过的源码都忘了,还是记录博客的方式能帮助自己更深入的思考和总结。
先研究 SpringBoot 的启动流程,这个部分预计总共会有三篇,分别研究:
-
SpringApplication的构造函数
-
SpringApplication 实例的 run 方法
-
SpringBoot 启动类上的注解
@SpringBootApplication
本篇是第一篇,研究SpringApplication 的构造函数。
1. 推断 web 环境
SpringBoot 应用程序的入口最终会先走到如下的构造函数中:
构造函数的前两步代码我们直接忽略,不重要,看第三行WebApplicationType.deduceFromClasspath()
,这一行是 SpringBoot 在推断当前程序的 web 环境,作用是:后续会根据这个 web 环境来决定使用哪一个 ApplicationContext 实现类。我们点进这个方法看看是怎么实现的。
可以看到这个方法实现很简单,就是 SpringBoot 在代码里写死了一些类名,根据运行环境中是否包含这些类来判断 web 环境。运行环境分为三种:
- Servlet
- Reactive
- None
2. 加载工厂配置文件
接下来是重点,SpringBoot 会加载并暂存容器的监听器和初始化器,那从哪里加载这些配置呢?我们来看代码
getSpringFactoriesInstances
这个方法里面有个不起眼的地方:SpringFactoriesLoader.loadFactoryNames(type, classLoader)
。这里就是 SpringBoot “约定大于配置”原则的体现之处,即 spring.factories 机制。这个机制约定了外部组件如何将 bean 灵活的加载进 ioc 容器,并让容器感知到,类似于java 原生的SPI机制。
我们打开这个方法看一下代码:
我们直接以 Debug 的方式来对org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
方法进行分析,看一下 Spring 是如何加载 SpringFactories 的,点进来这个类之后发现不能编辑了,原来这个类是 Spring-Core 包下的,已经不属于 SpringBoot 的范围了。
这个类的第一行有一个重要的常量,标识了Spring 约定的配置文件的目录地址,即META-INF/spring.factories
。
进入loadSpringFactories
方法打断点可以看到Spring就是去上述约定的地址读取工厂配置文件,而 SpringBoot 作为 Spring 工厂配置的使用方,也提供了自己的工厂配置文件
我们打开 debug 信息中的工厂配置文件,发现其中有清晰的注释,表明每一个配置的用途。其中包括前面提到的Initializers
和监听器Listeners
。配置文件的格式是一个接口,=
分割,后面是实现类,实现类可能有多个,多个的话用,\
分割。
将文件中的信息通过循环遍历都加载进来之后,会把他们都存到一个MultiValueMap
中缓存起来(所谓MultiValueMap
可以理解为 Map<T,List<V>>.因为“值”是由装在 List 中的多个值构成,所以称为“多值”Map),正好对应工厂配置文件中一个接口多个实现类的结构。
这里有一个关键的类是SpringFactoriesLoader
,这个类用于读取 classpath 下的所有 jar 包中的工厂配置,并将这些配置放入一个内存缓存中,避免频繁IO,提供按 key 方便的获取实现类的方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)
。实际上 SpringBoot 内部也确实频繁的使用到了SpringFactoriesLoader.loadFactoryNames
这个方法。后续在 SpringBoot 自动装配的时候,也会看到他:
3. 推断 main 方法
这一步就比较简单了,直接根据堆栈信息遍历,查看哪个方法中有名字是 main 的方法。
总结
以上就是 SpringApplication 的构造方法源码解析,总结了一张图: