无涯

无所谓无 无所谓有

SpringApplication.run发生了什么

前言

Spring boot这个项目无疑是Spring Project最成功的项目,它把本来就很强大的spring framework进行进一步封装简化,遵从约定大于配置的设计思想,开箱即用。把spring推向了一个新的高度。

今天我从spring boot的启动入口,分析一下它的启动流程。需要注意的是,我只讨论启动流程,其中很多细节不会展开。

启动一个Spring Boot Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


/**
* @author oneyoung
*/
@SpringBootApplication
public class DemoApplication {

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

启动一个spring boot应用就是这么简洁优雅,只需要简单几行代码,一个注解、一个类、一个main方法、一个静态方法。

SpringApplication#run(Class<?>, String…)

1
2
3
4
5
6
7
8
9
10
11
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

run有多个方法重载。实际调用的是下面这个

1
2
3
4
5
6
7
8
9
10
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

首先对SpringApplication进行实例化,传入启动类的Class,然后调用run的另外一个实例方法(注意这个run方法是非static)。方法最后返回ApplicationContext,也就是广义上的IOC容器对象。

那我们先看看SpringApplication是这么实例化的。

SpringApplication的实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #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();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

同样的,SpringApplication有多个重载构造器,最终调用的都是上面这个构造器

源码注释写到,创建一个SpringApplication对象,应用上下文会加载启动主类的bean,实例可以在执行run方法之前自定义。

WebApplicationType.deduceFromClasspath()这个方法可以根据启动类路径推断出该应用的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,

/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,

/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;

下面这个方法从spring factories获取BootstrapRegistryInitializer实例

spring factoriesspring boot设计的精髓所在,拿spring boot starter这个包举例子。

路径spring-boot-2.5.3.jar/META-INF/spring.factories spring.factories里面定义了很多需要加载的类,spring boot在启动的时候会扫描路径下面相关的spring.factories配置文件,并进行实话注入到IOC。

如果你写过Starter,这个肯定很熟悉!

1
2
3
4
5
6
7
8
9
10
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

加载过程:

org.springframework.core.io.support.SpringFactoriesLoader#loadFactories

有兴趣的同学可以自行阅读源码

1
2
3
4
5
6
7
8
9
@SuppressWarnings("deprecation")
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
getSpringFactoriesInstances(Bootstrapper.class).stream()
.map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
.forEach(initializers::add);
initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
return initializers;
}

然后先后加载了ApplicationContextInitializerApplicationListener并初始化为成员变量

run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

/**
* 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) {
// 时间计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 创建默认 DefaultBootstrapContext 并初始化 bootstrapRegistryInitializers
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 获取 SpringApplicationRunListeners 也是从spring.factories加载
SpringApplicationRunListeners listeners = getRunListeners(args);
// 开启监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 配置spring环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 初始化env
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}