上一篇分析了SpringBoot的启动流程,关键步骤总结:
- 构建
SpringApplication
对象,用于启动SpringBoot; - 从
spring.factories
配置文件中加载EventPublishingRunListener
对象用于在不同的启动阶段触发不同的生命周期事件; - 准备环境变量,包括系统变量,环境变量,命令行参数及配置文件(比如
application.properties
)等; - 创建容器
ApplicationContext
; - 为第4步创建的容器对象做一些初始化工作,准备一些容器属性值等,同时调用各个
ApplicationContextInitializer
的初始化方法来执行一些初始化逻辑等; - 刷新容器,这一步至关重要,是重点中的重点,太多复杂逻辑在这里实现;
- 调用
ApplicationRunner
和CommandLineRunner
的run方法,可以实现这两个接口在容器启动后来加载一些业务数据等;
在SpringBoot启动过程中,每个不同的启动阶段会分别触发不同的内置生命周期事件,然后相应的监听器会监听这些事件来执行一些初始化逻辑工作比如ConfigFileApplicationListener
会监听onApplicationEnvironmentPreparedEvent
事件来加载环境变量等。
1.SpringApplication对象的构建过程
之前在讲解SpringBoot的启动流程中,有看到新建了一个SpringApplication
对象用来启动SpringBoot项目。那么,就来看看SpringApplication
对象的构建过程,同时分析下SpringBoot自己实现的SPI机制。
开始分析SpringApplication
对象的构造过程,因为一个对象的构造无非就是在其构造函数里给它的一些成员属性赋值,很少包含其他额外的业务逻辑(当然有时候可能也会在构造函数里开启一些线程啥的)。那么,先来看下构造SpringApplication
对象时需要用到的一些成员属性:
1 | //SpringBoot的启动类即包含main函数的主类 |
可以看到构建SpringApplication
对象时主要是给上面代码中的六个成员属性赋值,接着来看SpringApplication
对象的构造过程。
先回到上一篇文章讲解的构建SpringApplication
对象的代码处:
1 | public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { |
跟进SpringApplication
的构造函数中:
1 | public SpringApplication(Class<?>... primarySources) { |
继续跟进SpringApplication
另一个构造函数:
1 | public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { |
可以看到构建SpringApplication
对象时其实就是给前面讲的6个SpringApplication
类的成员属性赋值而已,做一些初始化工作:
- 给
resourceLoader
属性赋值,resourceLoader
属性,资源加载器,此时传入的resourceLoader
参数为null
; - 给
primarySources
属性赋值,primarySources
属性即SpringApplication.run(SpringBootSampleNikoApplication.class,args);
中传入的SpringBootSampleNikoApplication.class
,该类为SpringBoot项目的启动类,主要通过该类来扫描Configuration
类加载bean
; - 给
webApplicationType
属性赋值,webApplicationType
属性,代表应用类型,根据classpath
存在的相应Application
类来判断。因为后面要根据webApplicationType
来确定创建哪种Environment
对象和创建哪种ApplicationContext
; - 给
initializers
属性赋值,initializers
属性为List<ApplicationContextInitializer<?>>
集合,利用SpringBoot的SPI机制从spring.factories
配置文件中加载,后面在初始化容器的时候会应用这些初始化器来执行一些初始化工作。SpringBoot自己实现的SPI机制比较重要; - 给
listeners
属性赋值,listeners
属性为List<ApplicationListener<?>>
集合,同样利用利用SpringBoot的SPI机制从spring.factories
配置文件中加载。因为SpringBoot启动过程中会在不同的阶段触发一些事件,所以这些加载的监听器们就是来监听SpringBoot启动过程中的一些生命周期事件的; - 给
mainApplicationClass
属性赋值,mainApplicationClass
属性表示包含main
函数的类,即这里要推断哪个类调用了main
函数,然后把这个类的全限定名赋值给mainApplicationClass
属性,用于后面启动流程中打印一些日志。
1.1 推断项目应用类型
接着分析构造SpringApplication
对象的第【3】
步WebApplicationType.deduceFromClasspath();
这句代码:
1 | public enum WebApplicationType { |
如上代码,根据classpath
判断应用类型,即通过反射加载classpath
判断指定的标志类存在与否来分别判断是Reactive
应用,Servlet
类型的web应用还是普通的应用。
1.2 推断哪个类调用了main函数
分析构造SpringApplication
对象的第【6】
步this.mainApplicationClass = deduceMainApplicationClass();
这句代码:
1 | private Class<?> deduceMainApplicationClass() { |
可以看到deduceMainApplicationClass
方法的主要作用就是从StackTraceElement
调用栈数组中获取哪个类调用了main
方法,然后再返回赋值给mainApplicationClass
属性,然后用于后面启动流程中打印一些日志。
2.SpringBoot的SPI机制原理
SpringBoot没有使用Java的SPI机制,而是自定义实现了一套自己的SPI机制。SpringBoot利用自定义实现的SPI机制可以加载初始化器实现类,监听器实现类和自动配置类等等。如果要添加自动配置类或自定义监听器,那么很重要的一步就是在spring.factories
中进行配置,然后才会被SpringBoot加载。
好了,那么接下来就来重点分析下SpringBoot是如何是实现自己的SPI机制的。
前面构造SpringApplication
对象的第【4】
步和第【5】
步代码,因为第【4】
步和第【5】
步都是利用SpringBoot的SPI机制来加载扩展实现类,这里只分析第【4】
步的setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这句代码,看看getSpringFactoriesInstances
方法中SpringBoot是如何实现自己的一套SPI来加载ApplicationContextInitializer
初始化器接口的扩展实现类的?
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { |
继续跟进重载的getSpringFactoriesInstances
方法:
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { |
可以看到,SpringBoot自定义实现的SPI机制代码中最重要的是上面代码的【1】
,【2】
,【3】
步,这3步下面分别进行重点分析。
2.1 获得类加载器
Java的SPI机制默认是利用线程上下文类加载器去加载扩展类的,那么,SpringBoot自己实现的SPI机制又是利用哪种类加载器去加载spring.factories
配置文件中的扩展实现类呢?
分析第【1】
步的ClassLoader classLoader = getClassLoader();
这句代码:
1 | public ClassLoader getClassLoader() { |
继续跟进getDefaultClassLoader
方法:
1 | // ClassUtils.java |
可以看到,原来SpringBoot的SPI机制中也是用线程上下文类加载器去加载spring.factories
文件中的扩展实现类的!
2.2 加载spring.factories配置文件中的SPI扩展类
再来看下第【2】
步中的SpringFactoriesLoader.loadFactoryNames(type, classLoader)
这句代码是如何加载spring.factories
配置文件中的SPI扩展类的?
1 | // SpringFactoriesLoader.java |
继续跟进loadSpringFactories
方法:
1 | // SpringFactoriesLoader.java |
如上代码,loadSpringFactories
方法主要做的事情就是利用之前获取的线程上下文类加载器将classpath
中的所有spring.factories
配置文件中所有SPI接口的所有扩展实现类给加载出来,然后放入缓存中。
注意,这里是一次性加载所有的SPI扩展实现类,所以之后根据SPI接口就直接从缓存中获取SPI扩展类了,就不用再次去spring.factories
配置文件中获取SPI接口对应的扩展实现类了。比如之后的获取ApplicationListener
,FailureAnalyzer
和EnableAutoConfiguration
接口的扩展实现类都直接从缓存中获取即可。
思考1: 这里为啥要一次性从
spring.factories
配置文件中获取所有的扩展类放入缓存中呢?而不是每次都是根据SPI接口去spring.factories
配置文件中获取呢?
思考2: 还记得之前讲的SpringBoot的自动配置源码时提到的
AutoConfigurationImportFilter
这个接口的作用吗?现在应该能更清楚的理解这个接口的作用了吧。
将所有的SPI扩展实现类加载出来后,此时再调用getOrDefault(factoryClassName, Collections.emptyList())
方法根据SPI接口名去筛选当前对应的扩展实现类,比如这里传入的factoryClassName
参数名为ApplicationContextInitializer
接口,那么这个接口将会作为key
从刚才缓存数据中取出ApplicationContextInitializer
接口对应的SPI扩展实现类。从spring.factories
中获取的ApplicationContextInitializer
接口对应的所有SPI扩展实现类。
2.3 实例化从spring.factories中加载的SPI扩展类
前面从spring.factories
中获取到ApplicationContextInitializer
接口对应的所有SPI扩展实现类后,此时会将这些SPI扩展类进行实例化。
再来看下前面的第【3】
步的实例化代码:List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
。
1 | private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { |
上面代码主要做的事情就是实例化SPI扩展类。
思考3: SpringBoot为何弃用Java的SPI而自定义了一套SPI?
总结
好了,知识点总结下:
- 分析了
SpringApplication
对象的构造过程; - 分析了SpringBoot自己实现的一套SPI机制。
评论加载中