SpringBoot的启动流程

前言

之前用SpringBoot的时候就很好奇,SpringBoot究竟是如何启动的呢?今天花了点时间稍微研究了一下,认真看下到底是如何实现的。

1
2
3
4
5
6
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

可以看到就是这个类的main方法启动了整个框架,主题就是一个SpringApplication类的一个静态的run方法,把当前类作为类对象进行了传入,并且还把参数也顺手传递了进去。就这么简单。所以重要的其实就是SpringApplication这个类。

除了这个类之外,还不要忽略了@SpringBootApplication这个注解,正是注解+代码结合在一起才让spring真正的启动了起来。

@SpringBootApplication

其实这个注解本质上就是@Configuration + @EnableAutoConfiguration + @ComponentScan,也就是我们理解了这三个注解基本就理解了启动过程的一半了。

@Configuration

这个注解其实就相当于声明这个类是一个配置类,相当于之前的spring的xml配置文件——即IoC容器的配置类。如果你理解spring的配置文件,这个也不难理解。

@EnableAutoConfiguration

简单来说,底层其实靠的就是@Import支持,而Import的本质就是引入配置文件。所以就是把所有符合自动配置的bean给加载到IoC容器中。

具体来说,其实就是把一个AutoConfigurationImportSelector放到了容器中,它的职能就是扫描所有的带有@Configuration的类,并且把它们加入到容器中。

@ComponentScan

在spring中,其实就是自动扫描并且把相对应的bean加入到容器中。

@Conditional

虽然这个注解并不算在启动三大将的注解中,但是通过它和它的兄弟们,我们可以按需加载一些配置,所以姑且拿出来讲一讲吧。

SpringApplication

首先站在全局的角度来高度浓缩一下启动流程:

  1. 收集好各种条件和回调的接口
  2. 创建并且准备好Environment
  3. 创建并且初始化ApplicationContext,加载配置等
  4. refresh一下,ApplicationContext启动完成,结束!

然后再根据实际的代码来跟踪一下,发现其实调用的是静态方法是这样子的:

1
2
3
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}

首先使用了该类自己的构造方法创建了一个对象,并且调用了对象的另外一个run方法,所以如果硬要说的话,必须先构造出SpringApplication,然后在调用它的run方法。

构造方法

1
2
3
4
5
6
7
8
9
public SpringApplication(Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.initialize(sources);
}

刨去一些初始化的东西,真正重要的其实是最后一句this.initialize(sources);,然后继续跟踪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}

// 判断是否是web应用
this.webEnvironment = this.deduceWebEnvironment();
// 设置好Initializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置好监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 找出主类,就是MainApplication.class
this.mainApplicationClass = this.deduceMainApplicationClass();
}

首先把重心放在if判断里面。可以看见是给类的成员变量source(一个hashset)增加了一个成员,即MainApplication.class。

然后判断一下当前是否是web环境,通过一个deduceWebEnvironment的方法来判断。这个方法判断逻辑也很简单,就是判断类加载器里面是否有特定的类。这个就不展开细讲了。

接下来就是设置好初始化器,并且做一些初始化的工作。

然后是一些应用程序事件监听器。

ApplicationContextInitializer

从这个名字里也不难得出结论,去spring.factories文件中读取指定的类,并且将它们进行实例初始化,并且设置成监听器,我跟踪了一下,有下面这些会被加载进来:

image-20200801014144223

一共有六个,但是打开文件可以看到:

image-20200801014444625

最后那两个怎么来的呢?

ApplicationListener

和上面的一样的,通过一样的方法,找到listener,并且进行实例初始化。

image-20200801014654589

一共有10个,然后去spring.factories去看看:

image-20200801014755013

和上面的一样,文件里只有9个,但是被实例化的却有10个。对比这两个我们不难发现,其实有一个autoconfigure包下面的也加了进来。这里我们先暂时不深究为什么会多出来。

这里要多说明一下,ApplicationListener就是用来监听ApplicationEvent的。

构造完成之后,执行之前

现在我们已经了解了,在构造函数中使用了initialize,而在这个方法中最主要的就是两件事:实例化一堆Initializer和一堆Listener。

Initializer比较好理解,就是完成初始化工作的。而Listener中有一个比较重要的,我特地把它的源代码拿出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface SpringApplicationRunListener {

// run方法执行就立马执行
void starting();

// 环境信息准备好之后创建,应该是在容器创建之前
void environmentPrepared(ConfigurableEnvironment var1);

// 容器创建完
void contextPrepared(ConfigurableApplicationContext var1);

void contextLoaded(ConfigurableApplicationContext var1);

// run方法结束之前会被调用
void finished(ConfigurableApplicationContext var1, Throwable var2);
}

其实看到这个接口的名字也知道了,就是用来监听SpringApplication的Run方法的。我目前只看到有一个EventPublishingRunListener的实现类。这个实现类用来在SpringBoot启动的不同阶段发布不同的事件,如果有哪个ApplicationListener对这些事件感兴趣,则可以接受并且处理。

更加具体的来说,它的实现类,会构造出一个一个的ApplicationEvent,并且把这个事件给广播出去。而我们之前说那些ApplicationListener就会有相应。

OK,到了这一步,算是真真正正的把这个SpringApplication给构造出来了。

执行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
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}

listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}

try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}

总得来说,确实比较长噢,那么稍微来整理一下。

  1. new了一个stopWatch,并且启动了它。这个东西就是一个计时器,通过获取系统时间来实现的。
  2. 第7行,获取了SpringApplicationRunListeners(注意这个s),然后启动了它。实现的逻辑就是对于内部所有的SpringApplicationRunListener都调用其starting方法(当然现在来说只有一个)
  3. 第12行,构造了应用的参数,并且封装了一下。
  4. 第13行,准备好Springboot所需环境
  5. 第16行,在这一步,终于把ApplicationContext给构造了出来。
  6. 第19行执行了refresh操作。
  7. 第21行,停止了计时器。
  8. 第35行,返回了IoC容器。

最最重要的逻辑应该就是创建IoC容器的部分了吧,也就是createApplicationContext方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}

可以看到很明显通过策略模式来选择创建了三种ApplicationContext,我们一般都会进入到SERVLET这个选项中,然后会为我们创建AnnotationConfigServletWebServerApplicationContext。

创建完成之后,马上执行了refresh操作,这个也是很值得回味的过程。

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
54
55
56
57
58
59
60
61
62
63
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
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();
}
}
}

这块的内容比较多,所以只是简单概括一下:首先可以看到是通过synchronized来进行同步的,然后就是BeanFactory的设置,BeanFactoryPostProcessor接口的执行、BeanPostProcessor接口的执行等等等。

然后是具体的逻辑部分。

####prepareRefresh

显然是在真正做refresh之前需要完成的事。

1
2
3
4
5
6
7
8
9
10
// 记录下时间,并且修改状态为启动状态
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);

// 空方法,交给子类实现。初始化占位符信息(我的理解是把{jdbc.password}给替换成真实的值)
initPropertySources();

// 验证其它必须存在的信息
getEnvironment().validateRequiredProperties();

prepareBeanFactory

这个方法主要做了这些事情:

  • 告知内部的bean factory使用classloader,设置bean表达式的解析器,设置PropertyEditor(用来把2018/08/08转成date的东西)
  • 为bean factory增加了一个ApplicationContextAwareProcessor,同时忽略了5个已经被自动注入的Aware接口。
  • 实现一些资源加载、国际化等bean。
  • 又加了一个ApplicationListenerDetector作为BeanPostProcessor,这个估计是新加的。
  • 注册一些别的bean到容器中。

postProcessBeanFactory

这里根据不同的容器会进行不同的操作。

invokeBeanFactoryPostProcessors

以下暂时省略。

总结

SpringBoot的启动流程其实只要抓住两个流程,分别是SpringApplication对象的建立和run方法的执行。

在对象建立的时候:

  • 判断是否是web环境
  • 从spring.fatories中找到所有的Initializer,并且设置到一个List中(仅仅如此)
  • 从spring.fatories中找到所有的listener,并且设置到另外一个List中(仅此而已)

在run方法执行的时候:

  • 构造一个stopWatch用来记录时间。
  • 找到SpringApplicationRunListener并且封装进SpringApplicationRunListeners
  • 初始化容器并且完成容器的一些工作。