Springboot 启动流程源码主流程分析
一 、 前半部分new SpringApplication(primarySources)
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- WebApplicationType.deduceFromClasspath(); 是根据所有加载的类中,是否存在下面的几个类,判断是什么类型的应用,设置到SpringApplication类的webApplicationType属性上,后面创建容器的时候(二中的6createApplicationContext() ),根据它来判断该创建创建那种类型的容器。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
- 设置初始化的类,从META-INF/spring.factories中获取ApplicationContextInitializer指定的所有类,设置到SpringApplication的initializers属性上。
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
这个里面getSpringFactoriesInstances 会获取META-INF/spring.factories 文件中指定的类,是获取所有当前应用中,jar包目录中有META-INF/spring.factories文件,而不是某一个jar包下的。下图是我调试时候的截图。后续还会调用很多次这种getSpringFactoriesInstances方法,都从MultiValueMap类型的缓存中取,只在第一次的时候从文件中读取,之后就缓存了。
3. 和2类似, 这次取得是ApplicationListener指定的类。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
- 根拒线程堆栈,找到main方法所在的类,主要用来打日志。
this.mainApplicationClass = deduceMainApplicationClass();
二 、接下来看看后半部分的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);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
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);
}
}
- 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
stopWatch.stop();
stopWatch是一个计时器,可以统计start到stop之间所用的时间。
- 设置系统属性java.awt.headless
this.configureHeadlessProperty();
这一句是表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能
- 加载容器启动监听器
SpringApplicationRunListeners listeners=this.getRunListeners(args);
listeners.starting();
这两句的作用是:
- 从META-INF/spring.factories 配置中获取org.springframework.boot.SpringApplicationRunListener指定的类,只有一个org.springframework.boot.context.event.EventPublishingRunListener。加载并实例化
- 加载并实例化以后,调用他们的starting()方法。这个方法里面发布了一个ApplicationStartingEvent()事件。这个事件有四个支持的监听者,如下图
依次调用每个监听者的onApplicationEvent()方法,例如LoggingApplicationListener的onApplicationEvent() 方法就是判断一下是那种日志系统,log4j、logback等等,然后执行一些日志系统初始化的工作。
- 获取配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
这几句的作用是获取系统的环境变量、系统属性、还有我们自己的application.properties文件中的配置,下图是调试时候的截图,可以看到application.properties中的配置就是在prepareEnvironment(listeners, applicationArguments);这一步读取进来的。
同时发布ApplicationEnvironmentPreparedEvent事件,调用每个监听者的监听方法。
- 这个打印logo
Banner printedBanner = printBanner(environment);
- 获取ConfigurableApplicationContext,就是根据1中设置的容器类型,创建容器类。
context = createApplicationContext();
- 加载org.springframework.boot.diagnostics.FailureAnalyzers 指定的类。这些类主要是在项目启动失败之后,处理异常,打印日志等,他是在启动异常的catch块中调用的:
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
- 准备容器。 这一步主要是在容器刷新之前的准备动作:把之前解析好的环境environment、容器监听器listeners、启动时的参数applicationArguments等属性设置到容器对象。其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。 容器能加载到启动类,就能解析到启动类上的注解,就能逐个的加载要扫描的类,包括各种配置类及他们的初始化工作。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境,包括各种变量
context.setEnvironment(environment);
//执行容器后置处理
postProcessApplicationContext(context);
//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
applyInitializers(context);
//发送容器已经准备好的事件,通知各监听器
listeners.contextPrepared(context);
//打印log
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
//设置banner
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
//获取我们的启动类指定的参数,可以是多个
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件。
listeners.contextLoaded(context);
}
- 刷新容器。 spring的 ioc 和 aop的功能就在这里实现。比较复杂。后面再来。
refreshContext(context);
- 刷新容器后的扩展接口, 设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
afterRefresh(context, applicationArguments);
- 发布容器启动结束事件,有两个监听者,但是什么事情都没有做。
listeners.started(context);
- 执行所有实现了ApplicationRunner、CommandLineRunner接口的类中的run方法,经常用来自定义在容器启动启动完成后立即要做的事情。
callRunners(context, applicationArguments);
- 继续发布org.springframework.boot.context.event.ApplicationReadyEvent事件,调用该事件的监听器的监听方法。
try {
listeners.running(context);
}
有两个作者我觉得写得比较好的,我也参考了许多:
https://blog.csdn.net/qq_26000415/column/info/18708 写得很详细
https://blog.csdn.net/woshilijiuyi/article/details/82219585 写得很精髓