用过 SpringBoot 的同学都知道,其程序的启动类是在一个main
方法中调用SpringApplication.run
方法执行的,如:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
SpringApplication初始化
先看看SpringApplication
的构造
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
《1》
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
《2》
this.webApplicationType = WebApplicationType.deduceFromClasspath();
《3》
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
《4》
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
《5》
this.mainApplicationClass = deduceMainApplicationClass();
}
主要的逻辑:
- 初始化Spring容器的配置类primarySources
- 推断应用程序的类型,进而根据应用程序的类型创建恰当的ApplicationContext
- 初始化指定的ApplicationContextInitializer列表
- 初始化指定的ApplicationListener列表
- 推断main class的类名称
决策WebApplicationType
具体第二步决策创建什么样的ApplicationContext
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private WebApplicationType deduceWebApplicationType() {
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;
}
根据 classpath 下是否存在某个特征类来决定是否应该创建一个为 Web 应用使用的ApplicationContext
类型。具体判断为:
- 如果仅存在 Reactive 的包,则为
WebApplicationType.REACTIVE
类型; - 如果 Servlet 和 Reactive的包都不存在,则为
WebApplicationType.NONE
类型; - 其他情况都为
WebApplicationType.SERVLET
类型。
获取ApplicationContextInitializer和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;
}
步骤:
- 通过
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法,在 META-INF/spring.factories 文件下查找ApplicationContextInitializer
类型对应的资源名称。 - 实例化上面的资源信息(初始化器)。
- 对初始化器根据
Ordered
接口或者@Order
注解进行排序。
SpringFactoriesLoader会读取META-INF/spring.factories文件中的配置。一个工程项目中可以同时有多个META-INF/spring.factories文件(每个jar中都能有一个)。
例如在spring-boot-autoconfigure和spring-boot两个jar的META-INF/spring.factories文件中,均有针对ApplicationContextInitializer的配置:
# spring-boot-autoconfigure jar中的META-INF/spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# spring-boot jar中的META-INF/spring.factories
# 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.web.context.ServerPortInfoApplicationContextInitializer
拿到配置文件之后,就进行第二步的实例化
获取ApplicationListener
的过程和这个是一样的,就不多哔哔了
推断主类
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;
}
通过拿到堆栈信息,遍历方法名,如果是main,则是启动类(长见识)
SpringApplication启动(run)
把启动过程分成三部分来说,第一部分是打印banner及之前,第二部分为容器启动相关的代码,第三部分为容器启动之后的代码
public ConfigurableApplicationContext run(String... args) {
《1》
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
《2》
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
《3》
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
应用启动准备阶段
- 方法执行伊始,会先创建一个StopWatch对象,该对象用于统计应用的启动时间。下图中的启动时间日志就是根据StopWatch对象统计的数据打印的
- 拿到SpringApplication初始化阶段生成的SpringApplicationRunListener
- 通知监听事件,应用开始启动了listeners.starting()
prepareEnvironment
加载属性配置。执行完成后,所有的environment
的属性都会加载进来,包括 application.properties 和外部的属性配置。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
应用启动
第二部分的逻辑
createApplicationContext
根据webApplicationType创建响应的ApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_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);
}
我们以 SERVLET 类型为例,它会创建AnnotationConfigServletWebServerApplicationContext
应用上下文实例。
获取 Spring 异常报告器
getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context)
方法,获取 META-INF/spring.factories 文件下类型为SpringBootExceptionReporter
的资源实例:
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
prepareContext
Spring 应用上下文启动前的准备工作:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置 context 的 environment 属性
context.setEnvironment(environment);
// Spring 应用上下文的后置处理
postProcessApplicationContext(context);
// 运用 Spring 应用上下文初始化器
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加载 BeanDefinition
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
大致流程为:
- 给
context
的属性做赋值,如设置环境变量,调用初始化器来初始化context
。 - 获取所有配置源信息,包括 Configuration Class、类名、包名及Spring XML 配置资源路径信息。
- 加载 Spring 应用上下文配置源。将
BeanDefinition
加载到context
中。 - 发布上下文已准备事件
ApplicationPreparedEvent
。
Spring Boot 中的BeanDefinitionLoader
是BeanDefinition
读取的综合实现。当其load()
方法调用时,各个BeanDefinitionReader
类型的属性各司其职,为 Spring 应用上下文从不同的配置源装载 Spring Bean 定义(BeanDefinition)。
ApplicationContext 启动阶段
这个后续专门写一篇吧,这里知道spring容器启动了就行
ApplicationContext 启动后阶段
这是一个空方法
应用启动完成
最主要有个callRunners方法,来调用实现了CommandLineRunner
或者ApplicationRunner
接口的类的 run 方法,得以满足需要在 Spring 应用上下文完全准备完毕后,执行一些操作的场景。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
总结
SpringApplication类将一个SpringBoot应用的启动流程模板化,并在启动过程中提供了一些扩展点让我们可以根据具体需求进行扩展(通过SpringApplicationRunListener机制)。
来源
https://segmentfault.com/a/1190000019560001
https://www.jianshu.com/p/c2789f1548ab
http://svip.iocoder.cn/Spring-Boot/SpringApplication/