一、启动类 - 新生成一个SpringApplication
以最简单的启动SpringBoot方式为例,
跟了两层以后会发现在这个地方使用静态方法创建了一个SpringApplication对象,并调用了它的 run 方法。
那我我们先来看下是怎么创建 SpringApplication对象的吧。
刚才这个静态方法下面又封装了一层,传入了一个null的resourceLoader,这个参数一定程度上可以理解为类加载器,传入为null时实际上最后用到类加载器的是线程的默认的,这个后面用到resourceLoader 的时候会讲。
再下面一层就是实际构造SpringApplication对象的构造器了。
今天我们的目标就是解释一下这个创建过程中每一行在做什么。比较简单的行就直接用注解的方式来说明了。
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
//STEP1:设置 resourceLoader,此处应该为null
this.resourceLoader = resourceLoader;
//STEP2:断言,primarySources不能为空
Assert.notNull(primarySources, "PrimarySources must not be null");
//STEP3:将 primarySources 进行去重,放到一个成员变量里面保存
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//STEP4:判断当前应用的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//STRP5:配置初始化器
setInitializers((Collection)
getSpringFactoriesInstances(ApplicationContextInitializer.class));
//STEP6:配置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//STEP7:通过栈信息查找执行main方法的主类
this.mainApplicationClass = deduceMainApplicationClass();
}
STEP1、2相对简单,这里就不多介绍了。STEP7可以跟一层源码,也很简单。
STEP4:
这一步是判断当前应用的类型。可以看到 WebApplicationType 是一个枚举类,主要用于判断当前应用的类型。
我们可以看到在这个枚举类里面定义了三种类型的web应用,第一种NONE代表不是Web项目,第二种就是我们常见的Servlet,第三种是Reactive。STEP4主要想做的就是判断当前的Web应用到底属于哪种类型。那么用什么方式来判断呢?我们可以从STEP4中的 WebApplicationType.deduceFromClasspath() 方法来看看
我们可以看到,这个方法判断web应用的策略就是:通过classLoader取找指定的类,再根据这个类查找的结果,来判断web应用的类型。
举个例子,我们都知道Servlet的核心类是 javax.servlet.Servlet,那我们仔细想想,反向推理的话,如果你的Web应用的classpath里面不包含这个类的话,是不是就可以断定它不是一个Web应用了?这个地方用的就是这种推理方法,品一品。
STEP5 & STEP6:
这两个步骤放在一起说,因为他们俩的逻辑都是一样的:从classpath中找到并读取META-INF/spring.factories文件,读取指定的配置后,实例化并设置到本地的成员变量。不同的是他们读取的配置不一样而已。
你在使用SpringBoot的时候有没有想过为什么我想集成一个Redis这种第三方组件这么简单?pom里面加一个starter,配置文件加几行就可以直接使用了?SpringBoot自动装配做了哪些事情?其实就和spring.factories 这个文件有关。这不是这篇文章讨论的重点,有兴趣的同学可以自己拓展下。
从方法的名字我们可以大概才出来它是干嘛的:读取 spring.factories 文件。
我们具体来分析这个方法。按照顺序我们分为四行去解析。
第一行:
getClassLoader()方法,获取一个类加载器。这个我们之前说过resourceLoader传的是个null,这里会用到它,我们看下这个方法的源码。
这里很明显了,如果 resourceLoader 不为空的话,那么就将这个对象中的ClassLoader返回。
我们在前面知道 resourceLoader 是null,所以看下面这个获取默认ClassLoader的逻辑。
可以看到在方法开始的时候就拿了当前线程的ClassLoader并返回,如果当前线程的ClassLoader仍为空的情况下,会返回一个系统默认的ClassLoader。
这个地方理解的比较浅显,你可以简单地认为,
getClassLoader() 这个方法无论如何都会返给你一个类加载器。
第二行:
前面有一行注释,说明这个结果是去重过的。
这个names里面放的是什么东西呢?我的理解是 spring.factories 文件中对应的配置项。
以STEP5为例,names这个Set里面放的应该是一系列配置Key为 ApplicationContextInitializer.class 全路径名 org.springframework.context.ApplicationContextInitializer 的Value。
上面这段话可能比较难理解,那么我们就来验证下吧。
我们在names这个地方打个断点,看下它的输出是什么?
直接启动SpringBoot就可以到这个断点,我们看到这里面有8个String类型的对象,那么这些对象从哪里来的呢?就是从spring.factories文件中来的。
下面我们来验证一下。
我们使用IDEA的全局搜索文件,可以看到有很多spring.factories。
我们选取其中一个,在
这个文件中按照刚才说的Key值 org.springframework.context.ApplicationContextInitializer 来搜索,
发现这几条与刚才断点调试的时候其中几条完全一致,这样我们就简单验证了一点:第二行的作用是从spring.properties文件中读取指定的配置文件。
这篇文章先更新到这里,后面一篇我们一起看下Spring是怎样读取 spring.properties 文件的,另外介绍下新建SpringApplication对象中STEP5&6剩下的部分。