我基于需求提出者或者提问者的角度去描述Spring boot的启动流程:
1、Springboot启动过程需要做什么?
以上几点是我们一般用户比较容易想到让Springboot帮我们做好的事情。
但是还有一些潜在的需求,比如:
1、使用过程中报错怎么办?能否自助排障?
Springboot作为一个需求开发者,当开发的系统出问题,总得要排查吧?总不能让使用者碰到错误就让开发人员去排查吧?所以Springboot为了让用户能够自助排查问题,把整个Springboot启动过程中的事件监控起来,即SpringApplicationRunListeners干的事情,它把执行过程以及执行失败的日志打印出来告知使用者。
启动流程主要分为三个部分:
第一部分:进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器
第二部分:实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
第三部分:是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论
SpringBoot的启动主要是通过实例化SpringApplication类,并调用run方法来启动的。
SpringApplication的构造方法做了几件事情:
如下会new SpringApplication对象,这个操作会调用初始化方法,
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
//sources即我们的启动文件,如Starter.java
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//确定是否是webEnvironment,后续会根据是否是web环境进行一些操作,如上下文初始化实例的选择(createApplicationContext)
this.webEnvironment = deduceWebEnvironment();
//上下文初始化:使用内部工具类SpringFactoriesLoader从META-INF/spring.factories加载加载所有ApplicationContextInitializer实现类并实例化,
//并将这些实例设置到SpringApplication的List> initializers中
//初始化实例包含:
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//监听实例初始化:从META-INF/spring.factories加载实现了ApplicationListener接口的类并生成实例对象,
//并将这些实例设置到SpringApplication的private List> listeners中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//获取main函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication会尝试帮你创建正确的ApplicationContext,默认情况下会使用AnnotationConfigApplicationContext或者 AnnotationConfigEmbeddedWebApplicationContext,这取决于你开发的是否是web环境。判断Web environment算法是非常简单的,直接基于某些类的存在与否。
如下:直接判断类路径下是否存在"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"两个类即可。
上下文实例详解:
//缺省 Web SpringApplication 应用会有以下 6 个 ApplicationContextInitializer:
//1.初始化任务委托给Environment指定的初始化器,
//相当于给外界提供了一个添加自定义ApplicationContextInitializer的入口
//委托给Environment属性context.initializer.classes下指定的ApplicationContextInitializer类,
DelegatingApplicationContextInitializer
//2.设置Spring ApplicationContext ID
//这些环境变量会被用来产生Spring ApplicationContext ID :
// spring.application.name,vcap.application.name,spring.config.name
// 如果没有找到以上属性设置,ID使用 application
ContextIdApplicationContextInitializer
//3.用于报告一般配置错误,
//添加BeanFactoryPostProcessor : ConfigurationWarningsPostProcessor
ConfigurationWarningsApplicationContextInitializer
//4.添加 ApplicationListener
ServerPortInfoApplicationContextInitializer
//5.在ConfigurationClassPostProcessor和Spring boot之间增加一个共享的CachingMetadataReaderFactory
//添加BeanFactoryPostProcessor : CachingMetadataReaderFactoryPostProcessor
SharedMetadataReaderFactoryContextInitializer
//6.将ConditionEvaluationReport写到log,在DEBUG级别下输出
//添加AutoConfigurationReportListener
AutoConfigurationReportLoggingInitializer
public ConfigurableApplicationContext run(String... args) {
//创建计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 声明 IOC 容器
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
//作用:即使没有检测到显示器,也允许其启动。对于服务器来说,是不需要显示器的,所以要这样设置
configureHeadlessProperty();
//准备运行时监听器EventPublishingRunListener,方式为从类路径下找到 META/INF/Spring.factories 获取 SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 封装命令行参数,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境,1、加载外部化配置的资源到environment;2、触发ApplicationEnvironmentPreparedEvent事件,
//创建环境完成后回调 SpringApplicationRunListeners#environmentPrepared()方法,表示环境准备完成
// 环境准备的主要工作就是 将系统属性配置及用户定义的属性配置 加载进来
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
// 打印banner图。banner即下面这坨东西,可以设置关掉打印,也可以修改
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
Banner printedBanner = printBanner(environment);
//创建应用上下文,决定创建web的ioc还是普通的ioc
context = createApplicationContext();
//容器启动失败分析,如端口被占用等
analyzers = new FailureAnalyzers(context);
//做context的准备工作,如把相关需要创建bean的类加载到上下文中
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文,
//很重要的就包含对上面被加载的bean进行实例化
//另外,@EnableAutoConfiguration相关配置也在这里将需要装配的bean给装配好
refreshContext(context);
//刷新Context容器之后处理
afterRefresh(context, applicationArguments);
//通过EventPublishingRunListener发布finished事件
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
SpringApplicationRunListener 的作用是在SpringApplication 的各个启动过程中,监听各个阶段的变化,并将每个阶段封装成事件(ApplicationEvent),发布出去。让其他监听这些事件的监听器能探测到,并调用到对应的处理方法。
1. SpringApplicationRunListener 是一个接口,它的主要实现类是 EventPublishingRunListener 类。主要作用是监听 SpringApplication 启动的各个阶段,并封装成对应的 ApplicationEvent ,并把它发布出去。
2. 这里运用到一个观察者模式,不懂的小朋友可以多百度一下。
3. 我们可以定义自己的SpringApplicationRunListener, 实现这个接口,然后再新建一个 META-INF/spring.factorie 文件,注册监听即可。
SpringApplicationRunListeners负责在SpringBoot启动的不同阶段, 广播出不同的消息, 传递给ApplicationListener监听器实现类。
SpringApplicationRunListeners从Java监听器的角度看,并不是一个监听器,只是个普通的Java类而已,可以去看这个类的定义。
而ApplicationListener是传统意义上的Java监听器,如实现类DelegatingApplicationListener, 实现了ApplicationListener,ApplicationListener继承了EventListener。
用来在对ApplicationContext
进行refresh操作之前对Application context进行一些初始化操作。
主要用来在ConfigurableApplicationContext#refresh()
之前对ConfigurableApplicationContext
进行一些初始化的操作。一般来说主要做一些程序化的操作,比如注册配置源,根据配置激活某个profile等。
基于观察者模式的Application的事件监听器。将ApplicationListener
注册到ApplicationContext
中,当有对应事件发生时,监听器会被调用。
ApplicationContextInitializer
和ApplicationListener
的加载是在类SpringApplication
的构造函数中完成的,具体都是通过调用函数getSpringFactoriesInstances(Class
来完成的。
在spring boot中默认配置的ApplicationListener
还挺多的,我们就摘抄几个比较重要的分析一下。
ConfigFileApplicationListener
类ConfigFileApplicationListener
是一个非常重要的监听器,除了是一个监听器之外,它实现了接口EnvironmentPostProcessor
。在监听到事件时,会从指定的文件加载配置并配置application context。
可以看到继承实现了接口EnvironmentPostProcessor
的方法postProcessEnvironment
,负责对SpringApplication的Environment进行处理。具体实现上可以看到创建了一个Loader,这个Loader会负责去javadoc描述得地方去查找文件并加载配置。
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
}
重点看一下方法onApplicationEnvironmentPreparedEvent
。在监听到ApplicationEnvironmentPreparedEvent事件之后,除了将自己加入到List
之外,还会调用函数loadPostProcessors
去加载在文件META-INF/spring.factories配置的EnvironmentPostProcessor
,最后执行所有的postProcessEnvironment
方法。
@EnableAutoConfiguration结合spring.factories实现了对外部依赖包的扫描。
具体参考我的另一篇博客:总结:Spring boot之@EnableAutoConfiguration
参考:
总结:Spring Boot 之spring.factories
Spring Boot创建Beans的过程分析
Springboot Feign整合源码解析
Spring Boot 中 @EnableXXX 注解的驱动逻辑
Spring Boot启动过程分析
SpringApplicationRunListener 是干啥的?
spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方
SpringApplication 的初始化过程分析 : initialize()
SpringBoot启动流程解析