[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?


简介


​Spring Boot​​​ 极大的减少了我们Spring项目开发的工作量,很多的配置文件往往都不需要编写了,只需要引入对应的​​starter​​​,就可以完成配置实例的自动装配。那么,在​​Spring Boot​​项目执行时,到底都做了哪些事呢?



目录



手机用户请​​横屏​​​获取最佳阅读体验,​​REFERENCES​​中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。


平台

地址



简书

​https://www.jianshu.com/u/3032cc862300​

个人博客

​https://yiyuery.github.io/NoteBooks/​



正文



项目依赖


dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compile project(':common-dependencies:common-web-template')

compile project(':spring-boot-examples:spring-boot-hello-starter')

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompile("org.junit.jupiter:junit-jupiter-api")
testRuntime("org.junit.jupiter:junit-jupiter-engine")
}


入口


@SpringBootApplication◊
public class BootAutoConfigApplication {

public static void main(String[] args) {
SpringApplication.run(BootAutoConfigApplication.class, args);
}
}

点击​​run​​我们可以看到一个最基础的构造器函数的调用:

​new SpringApplication(null,primarySources)​

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//[1] 自动根据加载的Dipatcher推断当前容器环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//[2] 设置应用于Spring的{@link ApplicationContextInitializer}的初始化监听器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//[3] 设置自动装配的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//[4] 获取当前启动的main方法
this.mainApplicationClass = deduceMainApplicationClass();
}

推断容器环境


代码 [1]


//WebApplicationType
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

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;
}

//ClassUtils
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
//[2.1]代码
forName(className, classLoader);
return true;
}
catch (IllegalAccessError err) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.getMessage(), err);
}
catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
return false;
}
}

代码[1.1]根据包路径直接加载​​Class​

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_监听器

如果包路径下对应的Class文件可以加载到,则匹配到对应的容器环境。

设置监听器和加载初始化启动类


代码 [2]、[3]


如何设置初始化器呢?实现​​ApplicationContextInitializer.class​​接口的实例类很多,如何选择加载哪些呢?

public class ContextIdApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
//...
}

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_02

看实际加载结果是6个,那么为什么加载这6个初始化器呢?

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_03

​pringFactoriesLoader.loadFactoryNames(type, classLoader))​

核心方法是这个,那么如果读取到的者6个类包路径的呢?

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_监听器_04

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}

try {
Enumeration<URL> urls = (classLoader != null ?
//FACTORIES_RESOURCE_LOCATION是个常量值,对应的内容为 "META-INF/spring.factories";
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

​META-INF/spring.factories​​,其实际配置的是 Spring Boot 启动后自动装配的一些实现类。这里我们猜测是由于项目引用了相关的jar包。

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_05

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_spring_06

回头看下项目的依赖是否真的含有​​spring-boot-autoconfigure​​​,发现​​spring-boot-starter​​包中果然是引用了的,印证了我们的猜想。

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_07

所以此处加载逻辑,其实是自动装配​​spring.factories​​​中定义的那些​​ApplicationContextInitializer​​接口的实现类。

代码 [2] 的思路理清楚了,对应代码[3]的实现也是一致的。


  • 首先获取​​ApplicationListener​​接口包路径
  • 然后加载所应用的包中是否包含该接口的自动装配的类。
  • 设置进当前启动对象的成员变量中,后续Spring容器加载时进行注入。

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_08

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_监听器_09

获取main方法


代码 [4]


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 Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_spring_10

核心Run方法

/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
// [1] 项目启动信息收集,记录一些启动耗时等信息
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// [2] 启动监听器加载
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// [3]运行参数解析获取
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// [4] 启动 Banner 打印
Banner printedBanner = printBanner(environment);
// [5] 创建应用上下文
context = createApplicationContext();
// 自动装配启动异常处理实例类
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// [6] 准备上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// [7] 刷新上下文
refreshContext(context);
// [8] 刷新后更新上下文中启动参数(模板方法,用于子类根据启动参数做些处理)
afterRefresh(context, applicationArguments);
// 停止启动信息收集
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// [9] 启动后更新监听器中实例上下文信息
listeners.started(context);
// [10] 启动 Runner
callRunners(context, applicationArguments);
}catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
// [11] 发布运行事件给监听器
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回应用上下文
return context;
}

项目启动信息收集


代码 [1]


[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_11

加载运行监听器


代码 [2] 加载​​SpringApplication​​执行相关的监听器


private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}

仍然是自动装配的加载方式,加载实现接口​​SpringApplicationRunListener​​的类:

public interface SpringApplicationRunListener {

/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
*/
void starting();

/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
*/
void environmentPrepared(ConfigurableEnvironment environment);

/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
void contextPrepared(ConfigurableApplicationContext context);

/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
void contextLoaded(ConfigurableApplicationContext context);

/**
* The context has been refreshed and the application has started but
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @since 2.0.0
*/
void started(ConfigurableApplicationContext context);

/**
* Called immediately before the run method finishes, when the application context has
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @since 2.0.0
*/
void running(ConfigurableApplicationContext context);

/**
* Called when a failure occurs when running the application.
* @param context the application context or {@code null} if a failure occurred before
* the context was created
* @param exception the failure
* @since 2.0.0
*/
void failed(ConfigurableApplicationContext context, Throwable exception);

}

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_spring_12

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_13

然后就是循环调用​​starting​​方法开启运行监听器:

public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}

准备启动环境


代码 [3] 运行参数解析获取


public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {

/**
* Create a new {@code SimpleCommandLinePropertySource} having the default name
* and backed by the given {@code String[]} of command line arguments.
* @see CommandLinePropertySource#COMMAND_LINE_PROPERTY_SOURCE_NAME
* @see CommandLinePropertySource#CommandLinePropertySource(Object)
*/
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
//...
}

解析平常我们执行jar的一些参数:如 ​​jar xxx-boot.jarm --server.port=8080​


代码 [3.1] 启动环境准备


//启动环境准备
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;
}
// 根据前文提到的类文件加载判断容器类型,此处根据获取到的不同类型返回不同的环境对象
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
//更新SpringApplicationRunListener的环境信息
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
//绑定运行环境到当前应用实例
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
}
catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}

初始化应用上下文


代码 [5] 创建应用上下文


根据前文提到的当前容器环境加载对应的上下文实例

protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
//AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
//AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
//AnnotationConfigApplicationContext
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);
}


代码 [6] 上下文准备,初始化之后需要填充环境信息,并注入到监听器中


private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//[6.1] 设置运行环境信息
context.setEnvironment(environment);
//[6.2] 加工上下文
postProcessApplicationContext(context);
//[6.3] 加载初始化监听器
applyInitializers(context);
//[6.4] 监听器中加载上下文信息,并广播事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//将解析到的启动参数对象注册到实例工厂,便于后续读取
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
//如果负责Banner打印的实例存在,这注册到实例工厂
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
//根据字段 allowBeanDefinitionOverriding 判断是否允许用相同的名称重新注册不同的定义
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载上下文
load(context, sources.toArray(new Object[0]));
//广播加载上下文完毕的容器事件
listeners.contextLoaded(context);
}


代码 [6.3] 加载初始化监听器


//此处用到的getInitializers()即前文通过自动装配加载的一些启动初始化器
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
//返回根据@Order排序后的启动初始化器
public Set<ApplicationContextInitializer<?>> getInitializers() {
return asUnmodifiableOrderedSet(this.initializers);
}


代码 [6.4] 监听器中加载上下文信息,并广播事件


前文提到了启动通过自动装配方式设置了​​EventPublishingRunListener​​​的实例,此处主要是通过​​Spring​​中的广播容器事件机制进行上下文准备完毕的事件通知。

public void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}


代码 [7] 刷新上下文


刷新上下文之后,根据字段​​registerShutdownHook​​判断是否需要向JVM运行时注册一个关闭挂钩,关闭这个上下文。

private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
//调用上下文的refresh方法
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
//AbstractApplicationContext.refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// 告诉子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 在上下文中准备要使用的实例工厂
prepareBeanFactory(beanFactory);

try {
// 告诉子类刷新内部的实例工厂
postProcessBeanFactory(beanFactory);

// 在上下文中获取备注册到实例工厂的处理器
invokeBeanFactoryPostProcessors(beanFactory);

// 注册一些拦截实例创建的处理器
registerBeanPostProcessors(beanFactory);

// 为当前上下文初始化消息源
initMessageSource();

// 为当前上下文初始化事件广播
initApplicationEventMulticaster();

// 在上下文的自定义实现子类中初始化一些特殊的实例
onRefresh();

// 检查监听器的实例并把她们注册到实例工厂
registerListeners();

// 实例化所有非懒加载的实例.
finishBeanFactoryInitialization(beanFactory);

// 最后一步:Spring容器内对应的事件发布
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

​AbstractApplicationContext​​​为​​spring-context​​中的类

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_监听器_14

启动运行监听器


代码 [9] 启动后更新监听器中实例上下文信息(发布事件)


public void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
//EventPublishingRunListener
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationStartedEvent(this.application, this.args, context));
}
//ApplicationStartedEvent
public ApplicationStartedEvent(SpringApplication application, String[] args,
ConfigurableApplicationContext context) {
super(application, args);
this.context = context;
}


代码 [10] 启动 Runner


private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
//获取上下文工厂中的ApplicationRunner启动类
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
//获取上下文工厂中的CommandLineRunner启动类
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);
}
}
}
//挨个根据启动参数进行调用
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}


代码 [11] 发布Spring Boot应用运行中事件给监听器


public void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
//EventPublishingRunListener.running
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(this.application, this.args, context));
}

总结

本文从SpringBootApplication,详细介绍了具体的执行流程。

[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_加载_15


更多


扫码关注​​架构探险之道​​,回复『源码』,获取本文相关源码和资源链接


[Spring Boot] 从 SpringBootApplication 谈谈 Spring Boot 启动时都做了哪些事?_spring_16