前言
前几天看着源码与网上的博客自己也理解并写了以下关于SpringBoot的自动装配原理的文章,今天突然想到,既然都看了自动装配了,那SpringBoot的启动流程又是什么样的?平时写代码都是有一个main函数,但是SpringBoot只有一段简单的启动代码:
@SpringBootApplication
public class StaffingSystemApplication {
public static void main(String[] args) {
SpringApplication.run(Demo.class, args);
}
}
这个run的流程是什么呢?
还是从入口看起
SpringBoot启动就是从这个run方法进行的,我们先看看里面究竟有什么:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
SpringApplication类构造方法
可以看到,run方法进入到后一个方法后,先创建了一个SpringApplication类,SpringApplication最终调用的构造方法如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断web环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置bootstrapRegistryInitializers
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
//设置Initializers,通过getSpringFactoriesInstances()得到需要设置的Initializer。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置Listeners,通过getSpringFactoriesInstances()从/META-INF/spring.factories中读取所有的ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//找出main函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
下面依次看一下其中几个函数:
public enum WebApplicationType{
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
//判断环境
static WebApplicationType deduceFromClasspath() {
//REACTIVE类型
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)) {
//非web环境类型
return WebApplicationType.NONE;
}
}
//普通SERVLET类型
return WebApplicationType.SERVLET;
}
}
//找到main所在的类
private Class<?> deduceMainApplicationClass() {
try {
//得到堆栈信息
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
//遍历堆栈,如果找到main类,将其加载出来后退出
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
小结一下,这个new SpringApplication
主要做了以下几件事:
1、判断当前的环境是REACTIVE、NONE还是SERVLET
2、设置bootstrapRegistryInitializers
3、通过
getSpringFactoriesInstances()
方法,从类路径下/META-INF/spring.factories
文件中获取所有的ApplicationContextInitializer,加入容器4、同样通过
getSpringFactoriesInstances()
方法,从类路径下/META-INF/spring.factories
文件中获取所有的ApplicationListener
,加入容器5、从堆栈中找出main函数所在的类,Class.ForName加载并初始化到内存
调用SpringApplication类的run方法
public ConfigurableApplicationContext run(String... args) {
//看起来像是计时用的
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
//上下文信息
ConfigurableApplicationContext context = null;
//似乎是设置系统属性,应该对理解没有影响
configureHeadlessProperty();
//读取类路径/META-INF/spring.factories中所有SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
//开启监听
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//封装console传进来的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印那个springboot的banner图
Banner printedBanner = printBanner(environment);
//创建ApplicationContext上下文信息
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//准备上下文环境,实例化bean对象
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文,创建Tomcat容器
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//监听器执行started,表示启动成功
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
上述的关键方法在于refreshContext(context)
,目前看得有一点懵,头昏脑胀得,以后再来具体仔细的补补内部的逻辑
再小结以下,这个run方法从源码中可以看到主要做了以下几件事:
1、获取/META-INFO/spring.factories中获取所有SpringApplicationRunListeners
2、循环启动SpringApplicationRunListener,执行starting()方法
3、包装console传过来的参数。
4、准备环境,准备完成后,会调用Listener.environmentPrepared()方法
5、打印banner图
6、根据环境,创建Spring上下文ApplicationContext
7、预备上下文环境,在初始化应用后,先获取所有的Initializer,然后会调用所有Initializer.initializer()方法,再接着会调用Listener.contextPrepared()方法。在准备环境完成后,调用Listener.contextLoaded()方法,告知Context已经加载完毕。
8、刷新上下文,ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat),初始化bean等操作
9、调用所有listeners.started()方法,表示启动成功
10、回调所有的ApplicationRunner和CommandLineRunner
11、返回Spring上下文SpringContext
总结
SpringBoot的run整个流程主要的部分大致就是上面的内容了,但目前有许多东西知识从名字推论和别人的博客知道的,源码和具体的意义可能还需要后面再看看
后记
后面发现一个很好的总结的流程图:SpringBoot启动结构图