1 SpringApplication对象的构建过程
本小节开始讲解SpringApplication
对象的构造过程,因为一个对象的构造无非就是在其构造函数里给它的一些成员属性赋值,很少包含其他额外的业务逻辑(当然有时候我们可能也会在构造函数里开启一些线程啥的)。那么,我们先来看下构造SpringApplication
对象时需要用到的一些成员属性哈:
1 | // SpringApplication.java |
可以看到构建SpringApplication
对象时主要是给上面代码中的六个成员属性赋值,现在我接着来看SpringApplication
对象的构造过程。
我们先回到上一篇文章讲解的构建SpringApplication
对象的代码处:
1 | // SpringApplication.java |
跟进SpringApplication
的构造函数中:
1 | // SpringApplication.java |
继续跟进SpringApplication
另一个构造函数:
1 | // SpringApplication.java |
可以看到构建SpringApplication
对象时其实就是给前面讲的6个SpringApplication
类的成员属性赋值而已,做一些初始化工作:
- 给
resourceLoader
属性赋值,resourceLoader
属性,资源加载器,此时传入的resourceLoader
参数为null
; - 给
primarySources
属性赋值,primarySources
属性即SpringApplication.run(MainApplication.class,args);
中传入的MainApplication.class
,该类为SpringBoot项目的启动类,主要通过该类来扫描Configuration
类加载bean
; - 给
webApplicationType
属性赋值,webApplicationType
属性,代表应用类型,根据classpath
存在的相应Application
类来判断。因为后面要根据webApplicationType
来确定创建哪种Environment
对象和创建哪种ApplicationContext
,详细分析请见后面的第3.1小节
; - 给
initializers
属性赋值,initializers
属性为List<ApplicationContextInitializer<?>>
集合,利用SpringBoot的SPI机制从spring.factories
配置文件中加载,后面在初始化容器的时候会应用这些初始化器来执行一些初始化工作。因为SpringBoot自己实现的SPI机制比较重要,因此独立成一小节来分析,详细分析请见后面的第4小节
; - 给
listeners
属性赋值,listeners
属性为List<ApplicationListener<?>>
集合,同样利用利用SpringBoot的SPI机制从spring.factories
配置文件中加载。因为SpringBoot启动过程中会在不同的阶段发射一些事件,所以这些加载的监听器们就是来监听SpringBoot启动过程中的一些生命周期事件的; - 给
mainApplicationClass
属性赋值,mainApplicationClass
属性表示包含main
函数的类,即这里要推断哪个类调用了main
函数,然后把这个类的全限定名赋值给mainApplicationClass
属性,用于后面启动流程中打印一些日志,详细分析见后面的第3.2小节
。
2.1 推断项目应用类型
我们接着分析构造SpringApplication
对象的第【3】
步WebApplicationType.deduceFromClasspath();
这句代码:
1 | // WebApplicationType.java |
如上代码,根据classpath
判断应用类型,即通过反射加载classpath
判断指定的标志类存在与否来分别判断是Reactive
应用,Servlet
类型的web应用还是普通的应用。
3.2 推断哪个类调用了main函数
我们先跳过构造SpringApplication
对象的第【4】
步和第【5】
步,先来分析构造SpringApplication
对象的第【6】
步this.mainApplicationClass = deduceMainApplicationClass();
这句代码:
1 | // SpringApplication.java |
可以看到deduceMainApplicationClass
方法的主要作用就是从StackTraceElement
调用栈数组中获取哪个类调用了main
方法,然后再返回赋值给mainApplicationClass
属性,然后用于后面启动流程中打印一些日志。
2 SpringBoot的SPI机制原理解读
由于SpringBoot的SPI机制是一个很重要的知识点,因此这里单独一小节来分析。我们都知道,SpringBoot没有使用Java的SPI机制(Java的SPI机制可以看看笔者的Java是如何实现自己的SPI机制的?,真的是干货满满),而是自定义实现了一套自己的SPI机制。SpringBoot利用自定义实现的SPI机制可以加载初始化器实现类,监听器实现类和自动配置类等等。如果我们要添加自动配置类或自定义监听器,那么我们很重要的一步就是在spring.factories
中进行配置,然后才会被SpringBoot加载。
好了,那么接下来我们就来重点分析下SpringBoot是如何是实现自己的SPI机制的。
这里接第3小节的构造SpringApplication
对象的第【4】
步和第【5】
步代码,因为第【4】
步和第【5】
步都是利用SpringBoot的SPI机制来加载扩展实现类,因此这里只分析第【4】
步的setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这句代码,看看getSpringFactoriesInstances
方法中SpringBoot是如何实现自己的一套SPI来加载ApplicationContextInitializer
初始化器接口的扩展实现类的?
1 | // SpringApplication.java |
继续跟进重载的getSpringFactoriesInstances
方法:
1 | // SpringApplication.java |
可以看到,SpringBoot自定义实现的SPI机制代码中最重要的是上面代码的【1】
,【2】
,【3】
步,这3步下面分别进行重点分析。
2.1 获得类加载器
还记得Java是如何实现自己的SPI机制的?这篇文章中Java的SPI机制默认是利用线程上下文类加载器去加载扩展类的,那么,SpringBoot自己实现的SPI机制又是利用哪种类加载器去加载spring.factories
配置文件中的扩展实现类呢?
我们直接看第【1】
步的ClassLoader classLoader = getClassLoader();
这句代码,先睹为快:
1 | // SpringApplication.java |
继续跟进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 | // SpringApplication.java |
上面代码很简单,主要做的事情就是实例化SPI扩展类。 好了,SpringBoot自定义的SPI机制就已经分析完了。
思考3: SpringBoot为何弃用Java的SPI而自定义了一套SPI?
3 小结
好了,本片就到此结束了,先将前面的知识点再总结下:
- 分析了
SpringApplication
对象的构造过程; - 分析了SpringBoot自己实现的一套SPI机制。