[SpringBoot] 源码解读(一)

1,085 阅读4分钟

从这篇文章开始,开一个新坑,解读(学习)一下 SpringBoot 的源码。

今天先从入口方法开始。

入口方法

所谓入口方法,就是 SpringApplication.run().

下面的源码解读还是基于一个非常简单的 SpringBoot 项目为例,下面是项目中唯一的 java 文件,也即是启动类:

package com.github.jwenjian.learning.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootLearningApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootLearningApplication.class, args);
	}

}

今天的源码分析就从 SpringApplication.run(SpringbootLearningApplication.class, args); 开始:

这个方法比较简单,就是一个静态的 Helper 方法,用于构建一个 SpringApplication 实例(1309 行)并调用这个实例的 run 方法。

构建 SpringApplication 实例

方法参数

  • ResoureLoaer, 传入了 null
  • Class..., 传入的是只包含入口类的 class 的数组

方法执行

  1. 279 行:将传入的 resourceLoader 赋给实例的属性

这一步没什么好说的,执行过后,该属性值为 null

  1. 280 行:判断传入的 class 数组,确保不为 null

一般来说,这个 class 数组不会为 null,但是因为这个构造方法是 public 的,不能排除有程序不通过入口方法而是直接调用该构造方法来创建实例,所以还是要做一层校验和保护

  1. 281 行: 将传入的 class 数组转为 set 并赋给实例的属性

一般来说,这个 class 数组只包含一个元素,就是入口类的 class。

  1. 282 行: 检测当前应用的类型,并将检测结果赋给实例的属性

WebApplicationType 是一个枚举类,包含 3 个实例: NONE,SERVLET,REACTIVE.

检测当前应用类型的方法是通过检测 classpath 中是否存在特定的类, 简单来说:

  • 如果存在 WebFlux 相关的类(DispatcherHandler) 而且 不存在 WebMVC 相关的类(DispatherServlet) 而且 不存在 JERSEY 相关的类(ServletContainer),则返回 REACTIVE

  • 如果不存在 Servlet 相关的类(Servlet 和 ConfigurableWebApplicationContext),返回 NONE

  • 如果以上都不满足,返回 SERVLET

  1. 283 行: 获取并实例化 Bootstraper 相关的类,之后将其赋给实例的属性

在这里着重说一下 getSpringFactoriesInstances 方法,因为接下来的两行代码也是用了这个方法来获取并实例化相关的类的。

这个方法接受了一个 class 对象(接口 class),根据不同的 class 类型获取并实例化相应的实例,具体做法如下:

  • 获取传入的接口 class 对象的全类名

  • 根据接口 class 对象的全类名,获取一个需要实例化的实现类 class 全类名列表,

    • 在某些 SpringBoot 的 jar 包中,有一个 META-INF/spring.factories 文件,定义了一些 接口全类名 = 实现类全类名列表 的键值对

    • 程序读取所有 META-INF/spring.factories 文件,将这些键值对存储到一个 HashMap 中

    • 根据传入的接口 class 的全类名,从 HashMap 中尝试获取对应的值,如果没有找到,则返回空集合

  • 遍历实现类 class 的全类名列表,对于每个实现类的 class,使用反射机制调用对应的构造方法来实例化一个对象,最终生成一个实例化对象列表

  • 对这个实例化对象列表进行排序

    • 如果一个对象实现了 Ordered 接口或者有 @Ordered 注解,获取这个对象对应的 order 值

    • 如果一个对象有 @Priority 注解,获取这个对象对应的 priority 值

    • 根据每个对象的 order 或者 priority 值,按升序排列

  1. 284 行: 获取并实例化 ApplicationContextInitializer 相关的类,之后将其赋给实例的属性

  2. 285 行: 获取并实例化 ApplicationListener 相关的类,之后将其赋给实例的属性

  3. 286 行: 检测主类 class 对象并赋给实例的属性

  • 检测的方法如下:

    • 遍历当前的调用栈

    • 找到某一个栈元素的方法名等于 "main", 该栈元素的 class 就是主类的 class


总结一下:

当程序调用入口类时,SpringBoot 会创建一个 SpringApplication 的实例并调用这个实例的 run 方法。

在创建实例时,SpringBoot 做了一些检测和实例化的动作:

  • 检测应用类型
  • 检测主类 class
  • 实例化 Bootstraper 相关的类
  • 实例化 ApplicationContextInitializer 相关的类
  • 实例化 ApplicationListener 相关的类

SpringApplication 的 run 方法源码分析将放在下面的系列文章中,敬请期待。