初心
为什么想学习 Spring Boot 源码?
第一点:我学习目的很功利,就是为了职业有更好地发展。简单地说,就是下一次换工作时,提高找到好公司的概率;有底气提出更高的工资。
第二点:通过学习源码,提高自己的工程思维、编码技巧等等编码实力。励志写出优雅的代码。
第三点:Spring Boot 是 Java 领域日常使用最多的框架。现在个人只是知道怎么用,对于其内部逻辑一窍不通,内心隐隐不安。
『深入学习 Spring Boot』计划通过输出倒逼输入的形式进行。因本人水平有限,本系列纯属学习笔记。学习过程中会广泛参考网络资料,包括但不限于,掘金、CSDN、博客园、慕课网、百度、谷歌等。
启动流程概览
整个 Spring Boot 容器启动时,基本分为三个大步骤:
graph LR
A(框架初始化) --> B(框架启动) --> C(自动装配)
下面分开,一步步地学习。
框架初始化
标准启动类如下:
@SpringBootApplication
public class InDepthSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(InDepthSpringBootApplication.class, args);
}
}
可以看到调用了 SpringApplication#run
,点进去继续看。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// 注意此处我们进入的是 new SpringApplication() 构造方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源加载器,null
this.resourceLoader = resourceLoader;
// 主类,一般是启动类
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 环境监测。判断启动的是 mvc 还是 webflux
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置系统初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 配置 main 函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
以上方法都在SpringApplication
类中,按照调用顺序排列。
在 SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
中的,便是框架初始化的逻辑。
这里我们需要重点注意两个方法,分别是 设置系统初始化器 与 设置监听器。后面会着重学习。
框架启动
SpringApplication
类创建好了,接下是调用run
方法,run
方法中就是核心的启动流程:
public ConfigurableApplicationContext run(String... args) {
// 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 参数准备
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 Headless 模式(无显示器、无键盘等系统配置)
configureHeadlessProperty();
// 发送启动事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 配置环境、发送环境配置完毕事件
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = printBanner(environment);
// 创建上下文
context = createApplicationContext();
// 初始化异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文(关联 Spring Boot 组件与应用上下文,发送contextPrepared事件,加载sources到context,发送contextLoaded事件等)
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文(实例化Bean等等)
refreshContext(context);
// 刷新后置工作
afterRefresh(context, applicationArguments);
// 停止计时器
stopWatch.stop();
// 日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发送started事件
listeners.started(context);
// 执行 runner 类
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 启动失败,处理异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发送running事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
上面就是 Spring Boot 启动时的核心方法,几乎每一行都是一个方法,其后隐藏着成百上千行复杂代码。还需要我们慢慢学习。
附框架启动图一张:
自动装配
自动装配实际上,是包含在 框架启动 中的。就是由于这是 Spring Boot 的一个非常非常非常重要的特性以及扩展点,所以还是独立为一个过程。
自动装配的逻辑主要在 **刷新上下文(refresh)**方法中。
自动装配本身的步骤比较少:
总结
这一篇,主要是从全局的角度,粗略地了解一下 Spring Boot 的启动过程。
重点在于,了解 Spring Boot 的启动粗略全过程,至于其中细节,我们后面慢慢学习。