SpringBoot启动流程
本章, 会粗略的讲一讲 main 函数 入口, 进入 springboot 启动流程
也仅仅是大概的提一下流程. 不会进行深入 . 勿喷
以后有空 会来细填这个坑的…
那么! 先把我总结出的流程列出来, 后面会跟上我的源码解析(粗略) 有不正确的地方请指出
总的流程 :
- main函数入口 , 调用SpringApplication的静态run方法
- 首先会对SpringApplication进行构造
- 会对基本的属性进行赋值
- 然后对当前的应用类型进行判断
- 通过反射得到 META-INF/spring.factories 里面的 ApplicationContextInitializer 和 ApplicationListener的实例
- 找到具有main方法的类
- 然后调用SpringApplcaiton实例的run方法
- 得到一些用来监听容器运行的类. 让他们工作起来 (META-INF/spring.factories)
- 对命令行参数进行包装, 得到一个可用的环境
- 打印横幅…
- 创建对应的上下文
- 创建一些异常的报告者
- 对上下文的启动前处理
- 上下文属性的设置
- 加载一些特殊的bean
- 解析资源
- 触发容器监听者的load回调方法
- 启动上下文的refresh方法, 让ioc容器工作起来 (明天有空 , 我也会进行理一理, refresh的粗略流程)
- 触发一个可扩展的钩子方法, 容器启动后~
- 回调容器监听者的 started 方法
- 如果都没有出错. 回调 监听者 的 running方法, 表示应用进入正常运行阶段~ 结束~
接下来就是 我根据源码 理解的一些注释了
main函数的流程
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
//SpringApplicaiton ------------------ . class
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
这是一个正常的spingboot启动入口
之后它会先创建 springapplicaiton 的应用
我们来看一下源代码先 ( 就挑出一些重要的 )
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// ... 省略掉了. 一堆属性 设置默认值...
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); //这个东西就是标注了@SpringBootApplication 的启动类 , 标记一下
// 判断当前是不是 web 应用
/** 有这3种类型
NONE,
SERVLET,
REACTIVE;
*/
// 通过判断当前类加载的范围内, 是否存在一些特殊的类 来进行判断
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 这俩注意 : 回去获得 类路径下的 META-INF/spring.factories 文件里的类名 , 进行反射获得实例 (这个东西 也是自动配置的原理之一)
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 找到具有main方法的类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
小总结 :
在创建SpringApplication 的过程中,
- 会对基本的属性进行赋值
- 然后对当前的应用类型进行判断
- 通过反射得到 META-INF/spring.factories 里面的 ApplicationContextInitializer 和 ApplicationListener的实例
- 找到具有main方法的类
既然有了一个 SpringApplication , 接着我们来看 run 方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); //主要用于记录 一些时间啊 啥的. 这里就忽略
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
//容器的监听者 ... 进行监听
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
// 这是命令行参数进行封装
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//准备出一个环境--↑↑↑
// 打印横幅... 就是 启动的时候 显示的 SpringBoot 的log图案
Banner printedBanner = this.printBanner(environment);
// 创造一个 上下文 这个方法会去看一眼...
context = this.createApplicationContext();
// 又是从 spring.factories 得到 这个 异常报告的 类...的实例集合
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 在这里 , 主要把 环境放入上下文中, 然后 设置一下上下文的一些属性, 把 applicationArguments 和 printedBanner 注册进 容器中 , 让上下文去加载资源 最后调用 listerners 的 contextLoaded 回调
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 这个方法会去调用 上下文十分有名的方法, refresh() .. 也就是到这里 ioc 容器就启动了
this.refreshContext(context);
// 这是一个空方法 , protected , 意味着 可以让我们自己去改写....扩展
this.afterRefresh(context, applicationArguments);
stopWatch.stop(); // 到这里位置 基本已经启动完成了
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context); //对容器 开始的事件 进行监听
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context); // 上下文启动了 事件
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
//创建上下文..
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass; //看看是不是由用户指定了一个上下文...
if (contextClass == null) { // 如果没有..
//其实这个方法就这点东西
/**
如果指定了 , 就用指定的
如果没有指定 --
且是SERVLET类型的, 就使用 AnnotationConfigServletWebServerApplicationContext
是REACTIVE类型的, 使用 AnnotationConfigReactiveWebServerApplicationContext
不是WEB应用, 就是使用 AnnotationConfigApplicationContext
*/
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
小总结 :
在这个run方法中, 我们大方向出发 ,完成了 上下文的创建, 资源的加载, 和上下文的启动 . 细节
- 得到一些用来监听容器运行的类. 让他们工作起来
- 对命令行参数进行包装, 得到一个可用的环境
- 打印横幅…
- 创建对应的上下文
- 创建一些异常的报告者
- 对上下文的启动前处理
- 上下文属性的设置
- 加载一些特殊的bean
- 解析资源
- 触发容器监听者的load回调方法
- 启动上下文的refresh方法, 让ioc容器工作起来
- 触发一个可扩展的钩子方法, 容器启动后~
- 回调容器监听者的 started 方法
- 如果都没有出错. 回调 监听者 的 running方法, 表示应用进入正常运行阶段~ 结束~