Spring源码分析(11)——SpringBoot源码

对SpringBoot源码的一些浅析。

SpringBoot的启动方式很简单,本文也是从以下代码入手

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

一、SpringBoot启动源码分析

本文介绍的SpringBoot版本是2.0.4,各版本的源码可能略有不同,但思路几乎是一致的。

SpringBoot的启动主要有两块代码,SpringApplication类的实例化和run()方法,这里只简述两个方法的主要内容。

SpringApplication构造器方法:

1、从classLoader中拿所有spring.factories文件,读取ApplicationContextInitializer配置

2、从classLoader中拿所有spring.factories文件,读取ApplicationListener配置

run()方法:

1、创建ApplicationContext容器,容器类型为AnnotationConfigServletWebServerApplicationContext

2、调用所有ApplicationContextInitializer

3、调用ApplicationContext的refresh()方法,完成对Bean的读取、解析、注册、实例化,以及tomcat容器的启动等(这步其实完全是ApplicationContext的东西)

总结:

从SpringBoot启动的源码上来看,SpringBoot主要是在ApplicationContext的基础上实现了spring.factories文件的支持,其他的东西都是ApplicationContext完成的,所以SpringBoot比较像ApplicationContext的“一层壳”。

SpringApplication.java
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
64
65
66
67
68
69
70
71
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 这里就是MyApplication
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断是否是web应用
this.webApplicationType = deduceWebApplicationType();
// spring.factories文件中寻找ApplicationContextInitializer的实现类,如org.springframework.context.ApplicationContextInitializer=com.**.PropertyInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// spring.factories文件中寻找ApplicationListener的实现类,如org.springframework.context.ApplicationListener=org.springframework.boot.context.config.DelegatingApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 寻找当前运行的类,这里就是MyApplication
this.mainApplicationClass = deduceMainApplicationClass();
}

public ConfigurableApplicationContext run(String... args) {
// 创建任务观察记录器,记录运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 获取RunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 封装成ApplicationStartedEvent,然后广播给刚才从spring.factories文件中初始化的ApplicationListener
listeners.starting();
try {
// 构造一个应用程序参数持有类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备系统运行环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
// 在控制台打印Spring banner
Banner printedBanner = printBanner(environment);
// 创建ApplicationContext容器,这里的类型是AnnotationConfigEmbeddedWebApplicationContext
// 注意,在普通Spring框架中,启动的类型是XmlWebApplicationContext,和SpringBoot的容器类型不一样
context = createApplicationContext();
// 创建SpringBoot错误报错类
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 对刚创建的ApplicationContext做一些预处理,包含对所有ApplicationContextInitializer的初始化调用
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// ApplicationContext容器的刷新,这里的内容可参见ApplicationContext的内容
// 所有Bean的读取、解析、注册、实例化,tomcat容器的启动等操作都是在这里完成的
refreshContext(context);
// 容器刷新的后处理
afterRefresh(context, applicationArguments);
// 停止任务观察记录器,停止记录运行时间
stopWatch.stop();
// 输出SpringBoot启动时间
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;
}

二、SpringBoot常见问题

  • SpringBoot启动了几个容器,怎么包装DispatcherServlet、ApplicationContext的?

1个,类型为AnnotationConfigServletWebServerApplicationContext

SpringMVC在web.xml中配置ContextLoaderListener启动一个根容器XmlWebApplicationContext,DispatcherServlet又启动了一个子容器XmlWebApplicationContext,以此来实现了Spring、SpringMVC父子容器的功能。

SpringBoot会启动AnnotationConfigServletWebServerApplicationContext作为根容器,DispatcherServlet也会初始化,但这时并不会再启动一个子容器,而是复用根容器,所以也不再有SpringMVC中父子容器的功能。

  • SpringBoot在哪初始化DispatcherServlet的?

在DispatcherServletAutoConfiguration类中有一个@Bean注解,SpringBoot扫描该注解初始化了DispatcherServlet。该类所在包名spring-boot-autoconfigure,来自于spring-boot-starter,是SpringBoot提供的开箱即用的包。

DispatcherServletAutoConfiguration.java
1
2
3
4
5
6
7
8
@Bean(name = {"dispatcherServlet"})
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
  • SpringBoot在哪启动Tomcat容器的,又怎么hold住线程的?

AnnotationConfigEmbeddedWebApplicationContext容器refresh的时候,会调用onRefresh方法。而AnnotationConfigServletWebServerApplicationContext的onRefresh方法会启动嵌入式的服务容器,比如tomcat或jetty。

有趣的是,SpringMVC是在容器初始化时就会初始化九大组件,而SpringBoot是在第一次请求过来时才会初始化九大组件,可以在DispatcherServlet#onRefresh方法打个断点验证此结论。

根容器实例化
onRefresh()
启动内嵌tomcat
DispatcherServlet
实例化
启动
启动结束
onRefresh()
初始化九大组件
Controller
实际方法处理
第一个请求过来
请求结束
Controller
实际方法处理
下一个请求过来
请求结束
  • SpringBoot、ApplicationContext、BeanFactory仅仅是一层一层的包装而已吗?

个人从宽松的概念看,还真是这样。

BeanFactory是最底层结构,实现了对Bean的注册、实例化等容器管理功能。

ApplicationContext是BeanFactory的上层抽象,在BeanFactory基础上做了一些BeanPostProcessor和BeanFactoryPostProcessor的注册等初始配置,BeanPostProcessor和BeanFactoryPostProcessor是两者之间的纽带,具体调用是BeanFactory去完成的。

SpringBoot又是对ApplicationContext的一层包装,提供了开箱即用的功能和对spring.factories文件的支持,而实际干活的都是ApplicationContext。

  • 怎么启动注解的,怎么扫描Bean的?

SpringBoot只是对ApplicationContext的一层包装,扫描注册Bean肯定是ApplicationContext做的咯,看ApplicationContext吧。

  • SpringBoot Starter怎么生效的?

项目中引入spring-boot-starter-parent,而spring-boot-starter-parent中又导入了spring-boot-dependencies作为parent。spring-boot-dependencies中使用了标签来定义了很多依赖的版本(并未实际引入),所以子pom.xml只需要在标签中引入一个依赖,都会继承父pom的版本,都不用显式定义版本,这样就完成了对版本的统一管理。

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
spring-boot-starter-parent-2.0.4.RELEASE.pom
1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
spring-boot-starter-parent-2.0.4.RELEASE.pom
1
2
3
4
5
6
7
8
9
10
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
......
</dependencies>
</dependencyManagement>

参考

https://github.com/fangjian0423/springboot-analysis

http://fangjian0423.github.io/categories/springboot/

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×