前言
我们今天来通过源码分析SpringBoot的启动流程,本文中所使用的SpringBoot的版本为2.7.12。
创建SpringApplication
进入run:
再次深入:
我们发现这里创建了一个SpringApplication对象,从最上面图可以看出,primarySources即我们最开始传入的启动类即主方法类。
进入SpringApplication的创建:
我将SpringApplication的创建分为了四个关键步骤:
第一个步骤很简单,即对SpringApplication对象的资源加载器和主方法类以及一些其他属性进行了填充。
确定服务类型
我们来查看第二个步骤,进入该方法:
isPresent方法:
我们发现isPresent方法应该是通过类加载器对指定的类路径进行了加载,如果加载成功即加载的类存在加返回true,加载出现错误就返回false。
那么步骤二就是通过类加载的形式判断指定类是否存在来确定web服务的类型,即REACTIVE、NONE、SERVLET。
创建注册初始化、上下文初始化以及监听器
进入步骤三的两个set方法:
可以看出步骤三应该是创建了BootstrapRegistryInitializer、ApplicationContextInitializer和ApplicationListener即注册初始化、上下文初始化以及监听器,然后填充到了SpringApplication对象中。
但是它们是怎么被创建的呢?我们可以发现它们都存在了一个相同的方法,传入不同的类,然后通过这个方法返回创建的对象:
我们进入查看:
进入loadFactoryName方法:
继续进入:
spring.factory:
仔细阅读,我们可以发现这个方法其实并不难,其主要是加载META-INF/spring.factories这个文件,然后读取每一个键值对的信息,然后最后将这些键值对放入一个map集合中,最后放入本地的缓存集合中。需要获取时先走缓存,从缓存中以要获取的类为键从缓存中获取。
我们通过loadFactoryName方法获取了每一个要加载的类的路径,那么就需要通过这些路径加载这个类,并创建实例,即createSpringFactoriesInstances方法:
这个方法会通过路径加载这些类然后通过反射创建这些实例。
步骤三:加载META-INF/spring.factories文件中的注册初始化、上下文初始化以及监听器配置,并通过反射创建实例,填充到SpringApplication中
确定启动类本身
步骤四则会通过运行栈来获取main方法所在的类,即启动类本身
run方法
创建SpringApplication对象完成web服务的创建以及注册初始化、上下文初始化和监听器和其他属性的创建和填充后,就调用其run方法:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
// 计时
stopWatch.start();
ConfigurableApplicationContext context = null;
// 设置系统Handless属性,表示没有显示器鼠标设备也会成功启动
this.configureHeadlessProperty();
// 创建运行时监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args); ...(1)
// 向监听器发送启动事件
listeners.starting();
try {
// 封装主方法传入的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 环境准备
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);...(2)
this.configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = this.printBanner(environment);
// 创建应用上下文
context = this.createApplicationContext();...(3)
// 刷新应用上下文前的准备
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);...(4)
// 刷新应用上下文
this.refreshContext(context);...(5)
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 var9) {
this.handleRunFailure(context, var9, listeners);
throw new IllegalStateException(var9);
}
try {
listeners.running(context);
return context;
} catch (Throwable var8) {
this.handleRunFailure(context, var8, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var8);
}
}
我挑选几个重要的方法带大家来深入源码查看
进入(1):
查看运行时监听器的初始化方法:
其封装了日志和通过方法传入的监听器
查看获取监听器的方法:
我们可以发现这两个方法很熟悉,就是我们上面介绍的,从spring.factories文件中加载运行时监听器,并进行封装。
环境准备
进入(2):
查看2(1):
该方法会根据在第一步进行的SpringApplication的初始化时确定的web应用服务,来创建不同相应的环境,我们来查看默认Servlet服务下的环境:
可以看到其包含两个属性,进行debug查看环境:
那么第一个方法其实就很简单理解了,其根据web服务创建相应的环境并填充了系统环境和JDK的环境信息。
查看第二个方法:
接着查看:
该方法将main方法传入的参数进行了封装并且设置到了环境中,如下图:
查看第二个方法:
该方法很简单,会确定当前的环境。
我们直接debug到最后,查看干了什么:
可以看到环境准备方法,添加了一些环境属性,并且其读取了配置文件application.yaml中的属性,对环境进行了设置
环境准备总结
环境准备阶段首先会根据web服务类型创建相应的环境,并且会填充相应的系统环境和JDK环境信息,以及填充主方法传入的参数,读取配置文件例如application.yaml等文件的属性进行填充。
创建应用上下文(容器)
进入(3):
这个方法相当简单,其会根据我们确认的web应用类型加载特定的上下文类,并且通过反射来创建应用上下文。