Spirng Boot2 系列教程(二十二)| 启动原理
一个读者,也是我的好朋友投稿的一篇关于 SpringBoot 启动原理的文章,才大二就如此优秀,未来可期。
我一直想了解一下 SpirngBoot 的是如何启动的,我想就来写一篇关于 SpirngBoot 启动分析吧。第一次写那么高深的技术话题理解不到位的话也请多多包涵。
源码版本
SpinrgBoot 2.0.2
众所周知 SpringBoot 的启动类是在一个 main 方法中调用 SpringApplication.run()
方法启动的,如:
@SpringBootApplication
public class DiveInSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(DiveInSpringBootApplication.class, args);
}
}
启动顺序分析如下:
初始化阶段 -> 运行阶段
初始化阶段:
进入run方法中,SpringApplication.run()
会先为其创建一个 SpringApplication 对象:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//加载应用资源(URL资源、File资源、ClassPath资源)
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// primarySources 为 run 方法传入的引导类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断Web应用类型
this.webApplicationType = deduceWebApplicationType();
//加载应用上下文(初始化Initializers)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//加载应用事件监听器(初始化ApplicationListener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断引导类
this.mainApplicationClass = deduceMainApplicationClass();
}
Step1. 通过 deduceWebApplicationType()
来推断我们Web类型应用
private WebApplicationType deduceWebApplicationType() {
//根据当前应用的ClassPath中是否存在相关实现类来推断Web类型
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
看看使用到的 3 个常量值:
常量值 | 应用类型 |
---|---|
REACTIVE_WEB_ENVIRONMENT_CLASS | org.springframework.web.reactive.DispatcherHandler |
MVC_WEB_ENVIRONMENT_CLASS | org.springframework.web.servlet.DispatcherServlet |
WEB_ENVIRONMENT_CLASSES |
也就是说,有如下三种情况:
-
如果应用程序中存在 org.springframework.web.reactive.DispatcherHandler 这个类,则表示是一个响应式 web 应用,项目在启动时,需要去加载启动内嵌的响应式 web 服务器。
-
如果应用程序中既不存在 javax.servlet.Servlet 类,也不存在org.springframework.web.context.ConfigurableWebApplicationContext 这个类,则
表示当前应用不是一个web应用,启动时无需加载启动内嵌的 web 服务器。 -
除上述两种情况外,则表示当前应用是一个 servlet 的 web 应用,启动时需要加载启动内嵌的 servlet 的 web 服务器(比如 Tomcat )。
Step2. 如何加载应用上下文初始器(初始化 Initializers )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
利用Spirng的工厂加载机制,实例化ApplicationContextInitializer实现类,并排序集合。具体实现方法如下:
- 通过
SpringFactoriesLoader.loadFactoryNames
来扫描META-INF/spring.factories
下符合 ApplicationContextInitializer 类型的资源名称。 - 实例化所有在
META-INF/spring.factories
下找到的资源信息 - 对实例化的资源信息进行优先级顺序排序,或通过
@order
注解和Ordered
接口进行排序
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#Spring容器的常见的错误配置警告
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#设置Spring应用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
Step3. 加载应用事件监听器( ApplicationListener )
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
利用 Spring 工厂加载机制,实例化 ApplicationListene r实现类,并排序对象集合,具体方法跟上面 初始化Initializers
类似,不赘述。
# Application Listeners
org.springframework.context.ApplicationListener=\
#Spring应用上下文加载完成之后清除缓存
org.springframework.boot.ClearCachesApplicationListener,\
#父容器关闭时通知各个子容器关闭,
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
#文件编码
org.springframework.boot.context.FileEncodingApplicationListener,\
#控制台彩色输出
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
#外部化配置 管理factories或者YMAL文件
org.springframework.boot.context.config.ConfigFileApplicationListener,\
#将指定事件广播给指定的监听器
org.springframework.boot.context.config.DelegatingApplicationListener,\
#将需要输出的日志打印到指定的级别 DEBUG INFO ERROR
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
#初始化日志系统
org.springframework.boot.context.logging.LoggingApplicationListener,\
#控制可执行Spirng文件版本
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
Step4. 推断引导类
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;
}
运行阶段
整个 SpringApplication 围绕着 run 这个方法并分为两个小阶段:
- 加载
SpringApplication
运行监听器,并监听Spring Boot
事件 - 创建 Spring 应用上下文和创建 Environment
public ConfigurableApplicationContext run(String... args) {
//记录运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//Spring 应用的上下文
ConfigurableApplicationContext context = null;
//记录启动期间的错误
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置文件加载及优先级判断
configureHeadlessProperty();
//获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
//加载运行监听器
listeners.starting();
try {
//创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//加载属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = printBanner(environment);
//创建应用上下文
context = createApplicationContext();
//实例化SpringBootExceptionReporter用于报告启动过程错误。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//初始化应用上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//刷新应用上下文(IOC容器的准备,初始化Bean)
refreshContext(context);
//应用上下刷新完成之后
afterRefresh(context, applicationArguments);
stopWatch.stop();
//启动日志记录器
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//启动运行监听器
listeners.started(context);
//启动后需要的操作
callRunners(context, applicationArguments);
....
}
}
Step1. 加载SpringApplication
运行监听器
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
利用 Spirng 的工厂加载机制,实例化 SpringApplicationRunListeners 实现类,并排序集合。具体实现方法如下:
-
通过
SpringFactoriesLoader.loadFactoryNames
来扫描META-INF/spring.factories
下符合 SpringApplicationRunListeners 类型的资源名称。 -
实例化所有在
META-INF/spring.factories
下找到的资源信息
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListenr
由此可见就只有 EventPublishingRunListenr
一个实现类
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
//实例化SimpleApplicationEventMulticaster事件发布者
this.initialMulticaster = new SimpleApplicationEventMulticaster();
//以迭代的方法逐一进行ApplicationListener的监听
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationStartedEvent(this.application, this.args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(this.application, this.args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,
this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
在 EventPublishingRunListener
实例化的时候,会实例化一个 SimpleApplicationEventMulticaster
事件发布者(它的作用就是监听容器中发布的事件,只要事件发生,就触发监听器的回调,来完成事件驱动开发),于是接下来调用 listeners.starting()
方法就会通过其内部的 initialMulticaster
属性发布 ApplicationStartingEvent
事件。
Step2. 创建Spirng应用上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//初始化阶段的推断Web类型
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableApplicationContext 实例
如果推断的为 SERVLETWeb 类型就实例化这个对象
web.servlet.context.AnnotationConfigServletWebServerApplicationContext
Step3. 创建 Environment
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建 ConfigurableEnvironment 对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置 ConfigurableEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
//将 ConfigurableEnvironment 绑定到 SpringApplication 中
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
根据初始化阶段的推断Web应用类型来创建对应的 ConfigurableEnvironment 实例。
至此整一个的 SpringBoot 过程已经分析完毕:我们来总结一下:
-
首先初始化 SpringApplication 类,并推断 WEB 启动类型,再初始化和实现应用事件监听器,然后推断引导类。
-
通过 SpringFactoriesLoader 加载的 SpringApplicationRunListener,调用它们的 started 方法。
-
根据 Web 服务类型创建不同的 Spring 应用上下文,并将之前准备好的 Environment 设置给 Spring 应用上下文 ApplicationContext 使用。
-
创建并配置当前 Spring Boot 应用将要使用的 Environment,如 applocation.properties 文件和外部配置。
-
SpirngBoot 开始启动。
推荐阅读
后语
如果看到这里,喜欢这篇文章的话,请转发、点赞。微信搜索「一个优秀的废人」,欢迎关注。
回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。
回复「电子书」送你 50+ 本 java 电子书。