上一篇分析了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机制。
评论加载中