一、总结
我基于需求提出者或者提问者的角度去描述Spring boot的启动流程:
1、Springboot启动过程需要做什么?
- 读取我们定义的配置文件。
- 如application-pro.properties,application.properties,logback-spring.xml等,因为我们希望程序按照我们的配置去执行。
- 换个说法,其实就是加载运行环境,也就是代码ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);做的事情
- 创建对象:包括我们自定义的类、引入的依赖包的类
- 比如我们的controller,Service注解的类,Component注解的类
- 比如依赖的RocketMQ组件,代码中使用的比如RocketMQTemplate对象,并不是我们自己创建的,而是Springboot帮创建的。
- 也就是代码:refreshContext(context);主要做的事情。
- 提供Web服务。
- 我们提供Web服务需要一个Web服务器,一般主流的入Tomcat,Jetty等。Springboot内置的Tomcat是如何启动并提供服务的?其实就是代码context = createApplicationContext();和refreshContext(context);做的事情。
以上几点是我们一般用户比较容易想到让Springboot帮我们做好的事情。
但是还有一些潜在的需求,比如:
1、使用过程中报错怎么办?能否自助排障?
Springboot作为一个需求开发者,当开发的系统出问题,总得要排查吧?总不能让使用者碰到错误就让开发人员去排查吧?所以Springboot为了让用户能够自助排查问题,把整个Springboot启动过程中的事件监控起来,即SpringApplicationRunListeners干的事情,它把执行过程以及执行失败的日志打印出来告知使用者。
二、启动流程描述
1、启动流程总览
启动流程主要分为三个部分:
第一部分:进行SpringApplication的初始化模块,配置一些基本的环境变量、资源、构造器、监听器
第二部分:实现了应用具体的启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块
第三部分:是自动化配置模块,该模块作为springboot自动配置核心,在后面的分析中会详细讨论
2、关于SpringApplication
SpringBoot的启动主要是通过实例化SpringApplication类,并调用run方法来启动的。
3、实例化SpringApplication时做了什么?
SpringApplication的构造方法做了几件事情:
- 推断WebApplicationType,主要思想就是在当前的classpath下搜索特定的类
- 搜索META-INF\spring.factories文件配置的ApplicationContextInitializer的实现类
- 搜索META-INF\spring.factories文件配置的ApplicationListener的实现类推断MainApplication的Class
4、SpringApplication的run方法做了什么?
- 创建一个StopWatch并执行start方法,这个类主要记录任务的执行时间
- 配置Headless属性,Headless模式是在缺少显示屏、键盘或者鼠标时候的系统配置
- 在文件META-INF\spring.factories中获取SpringApplicationRunListener接口的实现类EventPublishingRunListener,主要发布SpringApplicationEvent。说白了,SpringApplicationRunListener作用就是监听SpringApplication的run方法的运行情况的。具体参考:SpringApplicationRunListener 是干啥的?
- 把输入参数转成DefaultApplicationArguments类
- 创建Environment并设置比如环境信息,系统属性,输入参数和profile信息
- 打印Banner信息
- 创建Application的上下文,根据WebApplicationType来创建Context类,如果非web项目则创建AnnotationConfigApplicationContext,在构造方法中初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
- 在文件META-INF\spring.factories中获取SpringBootExceptionReporter接口的实现类FailureAnalyzers
- 准备application的上下文
- 初始化ApplicationContextInitializer
- 执行Initializer的contextPrepared方法,发布ApplicationContextInitializedEvent事件
- 如果延迟加载,在上下文添加处理器LazyInitializationBeanFactoryPostProcessor
- 执行加载方法,BeanDefinitionLoader.load方法,主要初始化了AnnotatedGenericBeanDefinition
- 执行Initializer的contextLoaded方法,发布ApplicationContextInitializedEvent事件
- 刷新上下文(加载tomcat容器),在这里真正加载bean到容器中。如果是web容器,会在onRefresh方法中创建一个Server并启动。
- 再刷新上下文
- 发布应用已经启动事件
- 发布应用启动完成事件。
四、核心启动源码之initialize
1、整体代码
如下会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<ApplicationContextInitializer<?>> initializers中
//初始化实例包含:
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//监听实例初始化:从META-INF/spring.factories加载实现了ApplicationListener接口的类并生成实例对象,
//并将这些实例设置到SpringApplication的private List<ApplicationListener<?>> listeners中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//获取main函数所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
2、springboot - web环境的推断
SpringApplication会尝试帮你创建正确的ApplicationContext,默认情况下会使用AnnotationConfigApplicationContext或者 AnnotationConfigEmbeddedWebApplicationContext,这取决于你开发的是否是web环境。判断Web environment算法是非常简单的,直接基于某些类的存在与否。
如下:直接判断类路径下是否存在"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"两个类即可。
3、上下文初始化
上下文实例详解:
//缺省 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<EmbeddedServletContainerInitializedEvent>
ServerPortInfoApplicationContextInitializer
//5.在ConfigurationClassPostProcessor和Spring boot之间增加一个共享的CachingMetadataReaderFactory
//添加BeanFactoryPostProcessor : CachingMetadataReaderFactoryPostProcessor
SharedMetadataReaderFactoryContextInitializer
//6.将ConditionEvaluationReport写到log,在DEBUG级别下输出
//添加AutoConfigurationReportListener
AutoConfigurationReportLoggingInitializer
4、监听器初始化
五、核心启动源码之run方法
1、整体代码
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);
}
}
2、SpringApplicationRunListeners
SpringApplicationRunListener 的作用是在SpringApplication 的各个启动过程中,监听各个阶段的变化,并将每个阶段封装成事件(ApplicationEvent),发布出去。让其他监听这些事件的监听器能探测到,并调用到对应的处理方法。
1. SpringApplicationRunListener 是一个接口,它的主要实现类是 EventPublishingRunListener 类。主要作用是监听 SpringApplication 启动的各个阶段,并封装成对应的 ApplicationEvent ,并把它发布出去。
2. 这里运用到一个观察者模式,不懂的小朋友可以多百度一下。
3. 我们可以定义自己的SpringApplicationRunListener, 实现这个接口,然后再新建一个 META-INF/spring.factorie 文件,注册监听即可。
3、SpringApplicationRunListeners与ApplicationListener的区别
SpringApplicationRunListeners负责在SpringBoot启动的不同阶段, 广播出不同的消息, 传递给ApplicationListener监听器实现类。
SpringApplicationRunListeners从Java监听器的角度看,并不是一个监听器,只是个普通的Java类而已,可以去看这个类的定义。
而ApplicationListener是传统意义上的Java监听器,如实现类DelegatingApplicationListener, 实现了ApplicationListener,ApplicationListener继承了EventListener。
4、ApplicationContextInitializer
用来在对ApplicationContext
进行refresh操作之前对Application context进行一些初始化操作。
主要用来在ConfigurableApplicationContext#refresh()
之前对ConfigurableApplicationContext
进行一些初始化的操作。一般来说主要做一些程序化的操作,比如注册配置源,根据配置激活某个profile等。
5、ApplicationListener
基于观察者模式的Application的事件监听器。将ApplicationListener
注册到ApplicationContext
中,当有对应事件发生时,监听器会被调用。
ApplicationContextInitializer
和ApplicationListener
的加载是在类SpringApplication
的构造函数中完成的,具体都是通过调用函数getSpringFactoriesInstances(Class<T> type)
来完成的。
在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<EnvironmentPostProcessor> 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<EnvironmentPostProcessor>
之外,还会调用函数loadPostProcessors
去加载在文件META-INF/spring.factories配置的EnvironmentPostProcessor
,最后执行所有的postProcessEnvironment
方法。
六、EnableAutoConfiguration
@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启动流程解析