springboot的启动流程源码分析
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | yangxiaohui227
来源 | urlify.cn/6jeaiu
测试项目,随便一个简单的springboot项目即可:
直接debug调试:
可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法:
1.SpringApplication对象的创建过程:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { //resourceLoader为null,因为我们没有传入,primarySources这里包含主启动类的ThymeleafApplication.class
this.resourceLoader = resourceLoader; //资源加载器,这里是null
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //将主启动类字节码存起来
this.webApplicationType = WebApplicationType.deduceFromClasspath(); //检测当前的项目web类型,后续会分析
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//这里涉及springboot的一个重要知识点,后续分析
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//这里涉及springboot的一个重要知识点,后续分析
this.mainApplicationClass = deduceMainApplicationClass();//这里检测main方法所在的类
}
通过SpringApplication的创建过程,我们分析下,它的主要几个方法:
this.webApplicationType = WebApplicationType.deduceFromClasspath();
因为我引入的是springboot-web相关依赖,所以,在本次测试项目中,webApplication的类型是AnnotationConfigServletWebServerApplicationContext
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))和setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):
后续很多地方也会用到这个功能,在此解析下:
所以上面2个方法分别是从spring.factories文件中,找到key为org.springframework.context.ApplicationContextInitializer 的所有值,然后创建对应的实例,另一个ApplicationListener同理
接下来我们分析下:this.mainApplicationClass = deduceMainApplicationClass();
检测main方法所在的类,我们自己写的代码,自己肯定很容易知道是哪个类,但springboot框架不知道,那怎么检测呢,我们先看一个异常栈信息:
如上图所示,我们只要从一个异常的堆栈中就可以获取到main方法了,所以源码检测main方法也是一样的:
至此SpringApplication对象创建完毕,后续我们分析下它的run方法都做了些啥:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); //一个计时器,用于计算启动的时间
stopWatch.start();//计时开始
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();//异常报告器列表
configureHeadlessProperty();//这个是给System对象设置一个属性:java.awt.headless=true
SpringApplicationRunListeners listeners = getRunListeners(args); //从spring.factories文件中读取SpringApplicationRunListener的实现类,并创建对应的实例,这个类后续分析
listeners.starting();//调用监听器的starting方法
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //这里仅仅是对请求参数进行封装成一个对象
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//创建并配置环境的一些属性,然后调用监听器的相应方法,后续会分析
configureIgnoreBeanInfo(environment);//往System对象中设置属性spring.beaninfo.ignore的值
Banner printedBanner = printBanner(environment);//打印启动的图标,后续分析
context = createApplicationContext();//在创建SpringApplication对象时,已经检测出当前环境是什么样的webAppliction的类型,这里就是创建该类型的实例,本次代码演示的是:AnnotationConfigServletWebServerApplicationContext
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);//从spring.factories文件中读取SpringBootExceptionReporter的实现类,并创建对应的实例
prepareContext(context, environment, listeners, applicationArguments, printedBanner);//为后续实例化所有的bean进行初始化工作,后续分析
refreshContext(context);//这里开始实例化所有的bean,也就是spring ioc的核心,调用父类的refresh()方法,同时对tomcat进行了实例化,这些源码我都有专门的博客分析过了,所以这里不再重复
afterRefresh(context, applicationArguments); //目前是空实现
stopWatch.stop(); //计时结束
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印本次启动花了多长时间:Root WebApplicationContext: initialization completed in 4186 ms
}
listeners.started(context);//调用Listenners的stared方法
callRunners(context, applicationArguments);//调用ApplicationRunner和CommandLineRunner的方法
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);//调用监听器的running方法
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
通过上面的分析,整个启动流程在原来spring启动项目的基础上,在不同的阶段,加上了监听器的各个方法调用,现在我们来详细分析上面的一些方法:
SpringApplicationRunListeners 和 SpringApplicationRunListener 关系,很明显,前者包含了多个后者:
class SpringApplicationRunListeners {
private final Log log;
private final List listeners;
SpringApplicationRunListeners(Log log, Collection listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting(); //开始启动,此时环境和上下文都还没开始启动
}
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment); //环境已经准备好了,我们可以通过实现对应的listener接口来修改环境中变量
}
}
void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context); //上下文已经准备好
}
}
void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);//上下文已经加载完毕
}
}
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context); //上下文已经启动完毕
}
}
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);//上下文启动完毕后,进入运行状态
}
}
void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
listener.failed(context, exception);//启动失败做些啥
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
可见SpringApplicationRunListener 会在不同的启动周期调用不同的方法,不必非常刻意理解每个方法的具体含义,我们可以根据每个方法在源码中的调用位置,自己实现一个SpringApplicationRunListener做一些个性化的修改
接下来,我们分析下:ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);方法,准备环境对象
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();//获取一个环境对象,不存在就创建
configureEnvironment(environment, applicationArguments.getSourceArgs());//进行命令行参数配置,我们命令行参数没数据是一个空数组,所以不会做什么,这里主要的设置了ConversionService对象到enviroment中
ConfigurationPropertySources.attach(environment);//配置configurationProperties
listeners.environmentPrepared(environment); //监听器处理环境准备好事件
bindToSpringApplication(environment);//绑定environment到SpringApplication中
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
下面看看Banner printedBanner = printBanner(environment); 效果如下:我们也可以自定义
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) { //如果是设置了不打印banner,这里就不会打印
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader()); //资源加载器
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); //创建banner打印器
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger); //打印到日志文件
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);//打印到控制台,这里我们以打印到控制台来根据
}
继续分析:bannerPrinter.print(environment, this.mainApplicationClass, System.out);
Banner print(Environment environment, Class sourceClass, PrintStream out) {
Banner banner = getBanner(environment); //从环境中获取banner
banner.printBanner(environment, sourceClass, out); //打印banner 注意Banner是一个接口,这里会根据子类来调用不同的打印方法,如图片banner和文本banner是不一样的
return new PrintedBanner(banner, sourceClass);
}
现在重点在于如何获取banner:getBanner(environment);
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment)); //获取图片banner
banners.addIfNotNull(getTextBanner(environment));//获取文本banner
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER; //都获取不到,就用默认的banner
}
//看看图片banner是如何获取的
接下来,我们看看文本banner的获取:
当加载不到默认的图片banner或者文本banner就会使用默认的banner,我们看看默认的banner是啥:
所以,如果我们要打印自定义的banner,只要在resources文件夹下加入banner.txt 或者banner.gif/banner.jpg/banner.png即可:
接下来,我们分析:prepareContext(context, environment, listeners, applicationArguments, printedBanner);准备上下文:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); //给context设置环境对象,context在这里是AnnotationConfigServletWebServerApplicationContext
postProcessApplicationContext(context);//这里根据条件给context设置一些重要的对象
applyInitializers(context);//Spring.factories文件中配置的ApplicationContextInitializer实现类,对context进行一些初始化操作,我们想对context进行特定操作也可以通过这种方式
listeners.contextPrepared(context);//监听器打印上下文已经准备好事件
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);//打印日志,当前启动的是那个yml “No active profile set, falling back to default profiles: default”
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();//获取bean工厂
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);//注册bean
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner); //注册banner这个bean,这里比较有趣了,后续你可以通过beanFactory获得该bean,然后继续打印banner
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set