highlight: a11y-dark
不管是工作还是面试 深入了解SpringBoot源码 都将给你带来非常实实在在的收获 so 今天我们来揭开SpringBoot
的第一个面纱(run方法
)。
1.初始化.搭建阅读环境 springboot版本为 2.1.x
github fork或者下载: https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE
#### 下载后 idea打开 由于springboo默认没将其写到父pom的modles中 所以我们将spring-boot-samples导入,执行 SampleTestApplication执行run方法(这里我选的是这个启动类)。
启动项目
报错 Kotlin: Language version 1.1 is no longer supported; please, use version 1.2 or greater.
解决方式
可能和idea版本有关系 我报了这个错 然后勾选idea这个选项就好了
- 注意其会有代码格式检查 需要关掉 在properties标签中加入 true
- 或者格式化下代码 mvn spring-javaformat:apply
2. SpringBoot main方法执行过程详解
注意:(由于一边调试一边写注释的话 debug会错位 造成调试不便 所以我在另一个项目中调试的源码版本都一样)
1. 启动run();方法
```java @SpringBootApplication public class SampleTestApplication {
// NOTE: this application will intentionally not start without MySQL, the test will // still run. // 启动入口 public static void main(String[] args) { SpringApplication.run(SampleTestApplication.class, args); }
} ```
2. 进入SpringApplication的构造方法
主要包含两个 1.上下文初始化对象 2.监听器对象
```java /** * 创建一个新的 {@link SpringApplication} 实例。该应用程序上下文将从指定的primarySources加载 bean * (有关详细信息,请参阅 {@link SpringApplication class-level} 文档。可以在调用之前自定义实例 * {@link #run(String...)}. * @param resourceLoader the resource loader to use * @param primarySources the primary bean sources * @see #run(Class, String[]) * @see #setSources(Set) * * -- 在该构造中 将创建上下文对象 * */
@SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { //初始化资源加载器,默认为null this.resourceLoader = resourceLoader; //校验 Assert.notNull(primarySources, "PrimarySources must not be null"); //初始化 primarySources 类并去重 一般我们就是一个即启动类 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //推断当前 WEB 应用类型,一共有三种:NONE,SERVLET,REACTIVE 默认SERVLET this.webApplicationType = WebApplicationType.deduceFromClasspath();
// <2.1> 设置应用上下文初始化器,从META-INF/spring.factories读取 ApplicationContextInitializer类对应的实例名称集合并去重(一共6个) 随后 利用反射工具进行对象的创建
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// <2.2> 设置监听器,从META-INF/spring.factories 读取ApplicationListener类的实例名称集合并去重。然后反射创建对象 其实和2.1过程很相似 唯一区别是 传入的参数 不同 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断主入口应用类,通过当前调用栈,获取Main方法所在类,并赋值给mainApplicationClass this.mainApplicationClass = deduceMainApplicationClass(); } ```
## 紧接着我们看下 <2.1> 处做了什么
可以看到其用的系统类加载器 关于类加载器 可聊得就太多了 这里不做展开了
- 接着使用 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)方法获取该类加载器下的所有spring.factories文件 名称 注意是名称还没到创建对象呢
```java private static Map > loadSpringFactories(@Nullable ClassLoader classLoader) { //根据类加载器先看看有没有 有直接返回 其实大部分情况都有 只有第一次调用该方法 也就是 构造SpringApplication上下文时候 需要加载当前包以及子包下的spring.factories文件 MultiValueMap result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //获取当前类加载器下的所有META-INF/spring.factories文件 Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //遍历properties 取出对饮的value 文件 while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); //这里的url我理解就是 文spring.factoies文件的全路径 事实也是这样的 UrlResource resource = new UrlResource(url); //根据文件的全路径 加载文件中的数据 Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
//遍历某个spring.factory.properties文件
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();