概述
用过Spring Boot的应该都知道,在项目启动入口的主类main()方法里,一句简简单单的
SpringApplication.run( ... );
便开启了项目的启动运行之路。
本文我们就来看看这个 SpringApplication 以及 run() 方法
SpringApplication
SpringApplication
这个类应该算是 Spring Boot 框架 本身的“创新”产物了,因为原始的Spring框架中并没有这个类,SpringApplication
里面封装了一套Spring应用的启动流程,然而这对用户完全透明,因此我们上手 Spring Boot 时感觉简洁且轻量。
一般来说默认的 SpringApplication
执行流程已经可以满足大部分需求,但是 若用户想干预这个过程,则可以通过 SpringApplication
在流程某些地方开启的 扩展点 来完成对流程的扩展,典型的扩展方案那就是使用 set
方法。
我们来举一个栗子,把我们天天司空见惯的 Spring Boot 应用的启动类来拆解一下写出来:
@SpringBootApplication
public class CodeSheepApplication {
public static void main( String[] args ) {
// SpringApplication.run( CodeSheepApplication.class args ); // 这是传统Spring Boot应用的启动,一行代码搞定,内部默认做了很多事
SpringApplication app = new SpringApplication( CodeSheepApplication.class );
app.setXXX( ... ); // 用户自定的扩展在此 !!!
app.run( args );
}
}
这样一拆解后我们发现,我们也需要先构造 SpringApplication
类对象,然后调用该对象的 run()
方法。
那么接下来就聊聊 SpringApplication
的构造过程 以及其 run()
方法的流程,搞清楚了这个,那么也就搞清楚了Spring Boot
应用是如何运行起来的了。
SpringApplication实例的初始化
/**
创建一个新的SpringApplication实例。应用程序上下文将从指定的主源加载bean(详细信息请参阅类级文档)。实例可以在调用run(String…)之前自定义。
参数:
resourcelloader -要使用的资源加载器
primarySources—主要bean源
参见:
运行(类,字符串[]),setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();//关键步骤一
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//关键步骤二
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 关键步骤三
this.mainApplicationClass = deduceMainApplicationClass();//关键步骤四
}
上述关键步骤解释如下:
步骤一:
推断应用的类型:创建的是REACTIVE应用、SERVLET应用、NONE三种的其中一个
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;//其中一个
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;//其中一个
}
}
return WebApplicationType.SERVLET;//其中一个
}
步骤二:
使用SpringFactoriesLoader
查找并加载classpath下的META/spring.factories
文件中所有可用的 ApplicationContextInitializer
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
步骤三:
使用SpringFactoriesLoader
查找并加载classpath下META-INF/spring.factories
文件中的所有可用的 ApplicationListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
步骤四:
推断并设置main方法的定义类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
Spring Application的run()方法探秘
/**
运行Spring应用程序,创建并刷新一个新的
{@link ApplicationContext}。
@param args应用程序参数(通常从Java主方法传递)
返回一个运行的应用程序上下文
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);//1
listeners.starting();//2
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//3 4
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();//5
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);//6
prepareContext(context, environment, listeners, applicationArguments, printedBanner);//7 8
refreshContext(context);//9
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);//10
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);//12
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
关键步骤都已经用数字标注在上述代码之中了,除此之外,这里也画了一个流程图对照理解:
我们将各步骤总结精炼如下:
- 通过
SpringFactoriesLoader
加载META-INF/spring.factories
文件,获取并创建SpringApplicationRunListener
对象 - 然后由
SpringApplicationRunListener
来发出starting
消息 - 创建参数,并配置当前
SpringBoot
应用将要使用的Environment
- 完成之后,依然由
SpringApplicationRunListener
来发出environmentPrepared
消息 - 创建
ApplicationContext
- 初始化
ApplicationContext
,并设置Environment
,加载相关配置等 - 由
SpringApplicationRunListener
来发出contextPrepared
消息,告知Spring Boot 应用使用的ApplicationContext
已准备OK - 将各种
beans
装载入ApplicationContext
,继续由SpringApplicationRunListener
来发出contextLoaded
消息,告知 Spring Boot 应用使用的ApplicationContext
已装填OK refresh ApplicationContext
,完成IoC容器可用的最后一步- 由
SpringApplicationRunListener
来发出started
消息 - 调用
callRunners(...)
方法,让实现了ApplicationRunner
和CommandLineRunner
接口类的run
方法得以执行,用于在 Spring 应用上下文准备完毕后,执行一些额外操作。从而完成最终的程序的启动。 - 由
SpringApplicationRunListener
来发出running
消息,告知程序已运行起来了
至此,全流程结束。