SpringBoot源码解读——启动篇

本文主要基于SpringBoot-2.1.0.RELEASE版本分析其启动过程,以及如何完成自动配置的过程。学习理解SpringBoot是如何将我们从繁重的配置文件中解救出来的。

为了演示重点和缩短文章,文中的代码引用通常会省略掉非讲解点的代码。

一、示例及入口

​ 一个典型的SpringBoot的应用的入口代码如下所示:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Boot2ExampleApplication {

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

}

​ 如果你了解过SpringBoot或者在哪里看到过SpringBoot应用的样子(你也可以在SPRING INITIALIZR生成一个最简单的模板应用),一定会惊叹于它的简洁与强大。

​ 从代码也能看出,所有有关SpringBoot的魔法,都隐藏@SpringBootApplicationSpringApplication.run中,接下来我们将从这两个概念入手,逐步分析出SpringBoot的运行原理。

二、@SpringBootApplication的解析

​ 这是SpringBoot应用的初始入口注解,定义的核心源码如下:

1
2
3
4
5
6
7
8
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 各种属性...
}

​ 可以看到它其实是一个组合其它注解的入口而已,其重点在于上面的3个注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。其中@ComponentScan可能大家很熟悉,它相当于我们之前在XML配置中的<context:component-scan>节点,主要用来扫描我们通过JavaConfig方式定义的SpringBean。而@EnableAutoConfiguration我们打开其源码会发现它只是一个标记接口,仅仅是开启了JavaConfig能力,并没有什么魔法。我们会重点讲解@EnableAutoConfiguration这个注解,因为SpringBoot的重点就在于自动配置,而最终的实现手段就来自于这个注解。

​ 如下是@EnableAutoConfiguration的核心源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};

}

​ 这个注解本身也没有什么实际的功能,仅仅只是定义了自动配置过程中想要exclude的类,而真正完成自动配置的过程都在它自身的注解@Import(AutoConfigurationImportSelector.class)上。

@Import我们知道是一个从其它JavaConfig类装载SpringBean定义的注解,所以我们的注意力要放在AutoConfigurationImportSelector,看看它里面有什么功能。其核心源码如下:

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
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 省略非关注点代码...
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 省略非关注点代码...
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 省略非关注点代码...
}

/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
// 省略非关注点代码...
}

/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
// 加载到所有 EnableAutoConfiguration 工厂
return EnableAutoConfiguration.class;
}

​ 一路看下来,我们发现所有的重点都落在了SpringFactoriesLoader.loadFactoryNames方法中,所做的一切就是从SpringFactoriesLoader中查找所有的EnableAutoConfiguration工厂。

​ 我们再看看SpringFactoriesLoader的核心代码如下所示:

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
public final class SpringFactoriesLoader {

/**
* 所有JAR包中的 spring.factories 位置
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
* <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
* <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
* to obtain all registered factory names.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
* @throws IllegalArgumentException if any factory implementation class cannot
* be loaded or if an error occurs while instantiating any factory
* @see #loadFactoryNames
*/
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
// 省略非关注点代码...
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
// 省略非关注点代码...
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
// 省略非关注点代码...
}

/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
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) {
// 省略非关注点代码...
// 扫描并加载所有的 spring.factories
try {
Enumeration<URL> urls = (classLoader != null ?
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;
}
// 省略非关注点代码...
}
}

​ 至此,@SpringBootApplication的核心代码执行完成,我们发现它仅仅只是做了一件事情:

扫描所有JAR包中 META-INF/spring.factories 文件中配置的 org.springframework.boot.autoconfigure.EnableAutoConfiguration来完成各个组件的自动化配置

​ 我们后面会讲解这个自动化配置的过程,已经spring.factories文件的格式、内容以及功能。我们暂时先放下@SpringBootApplication,先来看看SpringBoot魔法的另外一个支点SpringApplication.run的执行过程。

三、SpringApplication.run的解析

#### 关键路径源码分析

​ 在分析这个方法之前,我们先来看看SpringApplication这个类的构造方法,看看它做了什么事情,其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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();

// 注意如下的2行代码,分别用来设置容器的初始化工作和监听器的
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

this.mainApplicationClass = deduceMainApplicationClass();
}

​ 我们看到setInitializerssetListeners这两个方法其实都是调用了getSpringFactoriesInstances方法,只不过一个是获取初始化的组件,一个是获取监听器组件。我们跟进去看看这个方法是如何做的,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();

// 注意这行代码
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的调用,只不过这一次不是获取EnableAutoConfiguration,而是ApplicationContextInitializerApplicationListener。但是最终的来源仍然是:各个JAR包中的 spring.factories,这个文件大有玄机,后面我们重点分析,现在大家只要知道在初始化的时候会读取这个文件就行了。

​ 我们再来真正进入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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 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. 获取运行监听器并启动
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 2. 准备环境信息,并从环境信息中配置需要忽略掉的 Bean 配置
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);

// 3. 打印 Banner,这个可以自定义
Banner printedBanner = printBanner(environment);

context = createApplicationContext();

// 4. 获取异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

// 5. 准备 ApplicationContext 并刷新
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);

// 6. 通知监听器
listeners.started(context);

// 7. 执行 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

// 省略非关注点代码...
}

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 省略非关注点代码...

// 5.1 执行 SpringApplication 构造方法中收集到的 Initializer
applyInitializers(context);

// 省略非关注点代码...
}

​ 其中第 1,4 步故技重施地从 spring.factories 中读取了SpringApplicationRunListenerSpringBootExceptionReporter的配置。第 2,5,6 步是Spring容器的标准构建启动过程,在这里不做讲述,有兴趣的同学可以关注我对Spring源码的研究文章。

​ 我们重点讲解一下第 3 步Banner的处理和第 7 步 ApplicationRunner 的处理,因为这两个步骤在我们实际应用过程中经常会直接接触到。

​ 我们在启动一个SpringBoot的应用时,会在控制台打印如下的内容:

1
2
3
4
5
6
7
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.0.RELEASE)

​ 这个就是默认的Banner信息,我们从源码看看这个Banner内容是怎么生成及怎么定制,其核心源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

​ 可以看到是通过SpringApplicationBannerPrinter来打印这些信息的,那么这个类里面又是如何确定这些内容及如何去定制呢,核心代码如下:

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
// 文本类 Banner 的路径
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
// 图片类 Banner 的路径
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
// 默认的 Banner 信息存放的文件名
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
// 图片类 Banner 支持的文件后缀
static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
// 获取 Banner 信息
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}

private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
// 系统默认的 Banner
return DEFAULT_BANNER;
}

// 获取文本类 Banner
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}

// 获取图片类 Banner
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
}
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}

​ 可以看到,其默认的Banner信息是从classpath:banner.txt文件中读取的,那么我们只需要在这个路径的文件中填写我们想要的内容即可完成Banner的定制化了。

​ 同时,从源码我们可以看到,这个Banner是支持图片的,也就是我们可以指定一个图片文件作为Banner,SpringBoot会自动将图片转换成 ascII 字符图形来展示。

​ 更厉害的是,最近的SpringBoot Banner还可以支持动画,我们在图片文件后缀中看到了.gif,感兴趣的话可以在ImageBanner源码中学习SpringBoot是如何进行这个转换的。

​ 最后,我们看看系统默认的Banner类:

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
class SpringBootBanner implements Banner {

private static final String[] BANNER = { "",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )",
" ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };

private static final String SPRING_BOOT = " :: Spring Boot :: ";

private static final int STRAP_LINE_SIZE = 42;

@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE
- (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}

printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
printStream.println();
}
}

​ 是不是看到了我们熟悉的启动 Banner 图案。

ApplicationRunnerCommandLineRunner的使用

​ 这一节我们讲解在启动流程中的第 7 步:ApplicationRunner 是如何工作的。

​ 在启动流程中我们看到,这是整个启动流程的最后一个环节了:

1
2
// 7. 执行 ApplicationRunner
callRunners(context, applicationArguments);

​ 我们可以跟踪进去看看这个callRunners方法做了什么,其核心代码如下:

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
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();

// 从Spring容器中获取 ApplicationRunner
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());

// 从Spring容器中获取 CommandLineRunner
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());

// 经过排序后,依次执行这些接口的 run 方法
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);
}
}

​ 可以看到这是SpringBoot容器在启动完成后给我们提供的扩展接口,如果我们在实际的应用场景中有一些操作(比如数据初始化之类的)需要在容器启动后执行,那么这两个地方就是我们的扩展点。

好像是同样的功能点,SpringBoot提供了两个不同的操作,那么它们有什么区别,什么时候该用哪个呢?

答案是:没有区别

从源码中我们也看到了,唯一的区别是这两个接口接受参数的方式不同,一个是原始的String[],一个是格式化好的ApplicationArguments,用户可以根据自己的喜好和场景随意选择。

四、spring.factories 解析

​ 在前文中我们提到@SpringBootApplication的核心作用是扫描所有JAR包中 META-INF/spring.factories 文件中配置的 org.springframework.boot.autoconfigure.EnableAutoConfiguration来完成各个组件的自动化配置。同时,在SpringApplicaiton.run的启动过程中,也多次通过SpringFactoriesLoader.loadFactoryNamesMETA-INF/spring.factories中加载各类预定义的组件(初始化器、监听器等)。那么这个文件究竟长什么样,里面都有哪些东西呢?下面我们就通过真实的实例来讲解,如下代码提取(有省略)自spring-boot-autoconfig-2.1.0.RELEAST/META-INFO/spring.factories

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
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

​ 可以看到,这份主要分为 7 个部分,注意,这 7 个部分并非所有的 spring.factories 文件都会有,实际上每个部分都是可选的。这 7 个部分刚刚我们在源码分析中也见过一些了,分别是:

  • Initializers:应用初始化器,在应用初始化的时候执行。

  • Application Listeners:应用监听器,监听应用启动的各个事件。

  • Auto Configuration Import Listeners:自动配置引入时的监听器。

  • Auto Configuration Import Filters:自动配置时的过滤器。

  • Auto Configure:自动配置类,这个类别非常重要,它定义了各种需要自动配置的模板,这里只是摘取了一小部分,从源码中可以看到这个配置中包含了几乎所有目前能看到的(能集成)的框架,目前的版本一共有超过100个配置类。

  • Failure analyzers:这个定义了启动过程中的失败处理器,它将用于分析失败的原因,则将一些诊断信息所示给用户,协助用户解决问题。

  • Template availability providers:这里定义了一些模板引擎的支持,用于判定当前系统中是否支持一些模板引擎。非常遗憾的是,由于Velocity的疏于维护,在SpringBoot1.5的时候被除名,不再提供官方的支持了。需要的同学可以自行去扩展,或者联系我

我们可以随便打开一个auto configure项,例如我们在传统Spring应用中熟知的JdbcTemplateAutoConfiguration,源码如下:

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
@Configuration
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcTemplateAutoConfiguration {

@Configuration
static class JdbcTemplateConfiguration {

private final DataSource dataSource;

private final JdbcProperties properties;

JdbcTemplateConfiguration(DataSource dataSource, JdbcProperties properties) {
this.dataSource = dataSource;
this.properties = properties;
}

@Bean
@Primary
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource);
JdbcProperties.Template template = this.properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}

}
// 省略非关注点代码...
}

​ 可以看到,当系统中@ConditionalOnMissingBean(JdbcOperations.class)时,它会为我们创建一个JdbcTemplate,这样我们就可以直接在项目中使用 @Autowired 注入了。

实际上,spring.factories 只是一个普通的 properties文件,里面的内容也是可以随意定制的,只不过SpringBoot用作了自动配置,给我们提供了一个范例。这 7 个部分也仅仅只是 spring-boot-autoconfigure所需要的,很多其它的第三方JAR包中的 spring.factories 会定义很多自己需要的配置。

总结

​ 至此,我们以源码解读的试完整地讲解了整个SpringBoot的启动过程。讲解了如何从@SpringBootApplication注解找到所有自动配置的类并完成配置过程,也从SpringApplication.run的执行过程中重现了SpringBoot应用在启动过程中的各个生命周期。也讲解了项目中常见的扩展点,以及如何去扩展我们的应用能力。