本文主要基于SpringBoot-2.1.0.RELEASE版本分析其启动过程,以及如何完成自动配置的过程。学习理解SpringBoot是如何将我们从繁重的配置文件中解救出来的。
为了演示重点和缩短文章,文中的代码引用通常会省略掉非讲解点的代码。
一、示例及入口
一个典型的SpringBoot的应用的入口代码如下所示:
1 |
|
如果你了解过SpringBoot或者在哪里看到过SpringBoot应用的样子(你也可以在SPRING INITIALIZR生成一个最简单的模板应用),一定会惊叹于它的简洁与强大。
从代码也能看出,所有有关SpringBoot的魔法,都隐藏@SpringBootApplication
和SpringApplication.run
中,接下来我们将从这两个概念入手,逐步分析出SpringBoot的运行原理。
二、@SpringBootApplication
的解析
这是SpringBoot应用的初始入口注解,定义的核心源码如下:
1 |
|
可以看到它其实是一个组合其它注解的入口而已,其重点在于上面的3个注解:@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
。其中@ComponentScan
可能大家很熟悉,它相当于我们之前在XML配置中的<context:component-scan>
节点,主要用来扫描我们通过JavaConfig方式定义的SpringBean。而@EnableAutoConfiguration
我们打开其源码会发现它只是一个标记接口,仅仅是开启了JavaConfig能力,并没有什么魔法。我们会重点讲解@EnableAutoConfiguration
这个注解,因为SpringBoot的重点就在于自动配置,而最终的实现手段就来自于这个注解。
如下是@EnableAutoConfiguration
的核心源码:
1 |
|
这个注解本身也没有什么实际的功能,仅仅只是定义了自动配置过程中想要exclude的类,而真正完成自动配置的过程都在它自身的注解@Import(AutoConfigurationImportSelector.class)
上。
@Import
我们知道是一个从其它JavaConfig类装载SpringBean定义的注解,所以我们的注意力要放在AutoConfigurationImportSelector
,看看它里面有什么功能。其核心源码如下:
1 |
|
一路看下来,我们发现所有的重点都落在了SpringFactoriesLoader.loadFactoryNames
方法中,所做的一切就是从SpringFactoriesLoader
中查找所有的EnableAutoConfiguration
工厂。
我们再看看SpringFactoriesLoader
的核心代码如下所示:
1 | public final class SpringFactoriesLoader { |
至此,@SpringBootApplication
的核心代码执行完成,我们发现它仅仅只是做了一件事情:
扫描所有JAR包中 META-INF/spring.factories 文件中配置的
org.springframework.boot.autoconfigure.EnableAutoConfiguration
来完成各个组件的自动化配置
我们后面会讲解这个自动化配置的过程,已经spring.factories文件的格式、内容以及功能。我们暂时先放下@SpringBootApplication
,先来看看SpringBoot魔法的另外一个支点SpringApplication.run
的执行过程。
三、SpringApplication.run
的解析
#### 关键路径源码分析
在分析这个方法之前,我们先来看看SpringApplication
这个类的构造方法,看看它做了什么事情,其核心代码如下:
1 | public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { |
我们看到setInitializers
和setListeners
这两个方法其实都是调用了getSpringFactoriesInstances
方法,只不过一个是获取初始化的组件,一个是获取监听器组件。我们跟进去看看这个方法是如何做的,核心代码如下:
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, |
怎么样,是不是很眼熟,我们又一次看到了SpringFactoriesLoader.loadFactoryNames
的调用,只不过这一次不是获取EnableAutoConfiguration
,而是ApplicationContextInitializer
和ApplicationListener
。但是最终的来源仍然是:各个JAR包中的 spring.factories,这个文件大有玄机,后面我们重点分析,现在大家只要知道在初始化的时候会读取这个文件就行了。
我们再来真正进入SpringApplication.run
方法看看它做了什么,辗转几次之后,其核心代码如下:
1 | /** |
其中第 1,4 步故技重施地从 spring.factories 中读取了SpringApplicationRunListener
和SpringBootExceptionReporter
的配置。第 2,5,6 步是Spring容器的标准构建启动过程,在这里不做讲述,有兴趣的同学可以关注我对Spring源码的研究文章。
我们重点讲解一下第 3 步Banner的处理和第 7 步 ApplicationRunner 的处理,因为这两个步骤在我们实际应用过程中经常会直接接触到。
Banner 的处理
我们在启动一个SpringBoot的应用时,会在控制台打印如下的内容:
1 | . ____ _ __ _ _ |
这个就是默认的Banner信息,我们从源码看看这个Banner内容是怎么生成及怎么定制,其核心源码如下:
1 | private Banner printBanner(ConfigurableEnvironment environment) { |
可以看到是通过SpringApplicationBannerPrinter
来打印这些信息的,那么这个类里面又是如何确定这些内容及如何去定制呢,核心代码如下:
1 | // 文本类 Banner 的路径 |
可以看到,其默认的Banner信息是从classpath:banner.txt
文件中读取的,那么我们只需要在这个路径的文件中填写我们想要的内容即可完成Banner的定制化了。
同时,从源码我们可以看到,这个Banner是支持图片的,也就是我们可以指定一个图片文件作为Banner,SpringBoot会自动将图片转换成 ascII 字符图形来展示。
更厉害的是,最近的SpringBoot Banner还可以支持动画,我们在图片文件后缀中看到了.gif
,感兴趣的话可以在ImageBanner
源码中学习SpringBoot是如何进行这个转换的。
最后,我们看看系统默认的Banner类:
1 | class SpringBootBanner implements Banner { |
是不是看到了我们熟悉的启动 Banner 图案。
ApplicationRunner
与CommandLineRunner
的使用
这一节我们讲解在启动流程中的第 7 步:ApplicationRunner 是如何工作的。
在启动流程中我们看到,这是整个启动流程的最后一个环节了:
1 | // 7. 执行 ApplicationRunner |
我们可以跟踪进去看看这个callRunners
方法做了什么,其核心代码如下:
1 | private void callRunners(ApplicationContext context, ApplicationArguments args) { |
可以看到这是SpringBoot容器在启动完成后给我们提供的扩展接口,如果我们在实际的应用场景中有一些操作(比如数据初始化之类的)需要在容器启动后执行,那么这两个地方就是我们的扩展点。
好像是同样的功能点,SpringBoot提供了两个不同的操作,那么它们有什么区别,什么时候该用哪个呢?
答案是:没有区别
从源码中我们也看到了,唯一的区别是这两个接口接受参数的方式不同,一个是原始的
String[]
,一个是格式化好的ApplicationArguments
,用户可以根据自己的喜好和场景随意选择。
四、spring.factories
解析
在前文中我们提到@SpringBootApplication
的核心作用是扫描所有JAR包中 META-INF/spring.factories 文件中配置的 org.springframework.boot.autoconfigure.EnableAutoConfiguration
来完成各个组件的自动化配置。同时,在SpringApplicaiton.run
的启动过程中,也多次通过SpringFactoriesLoader.loadFactoryNames
从META-INF/spring.factories中加载各类预定义的组件(初始化器、监听器等)。那么这个文件究竟长什么样,里面都有哪些东西呢?下面我们就通过真实的实例来讲解,如下代码提取(有省略)自spring-boot-autoconfig-2.1.0.RELEAST/META-INFO/spring.factories
:
1 | # Initializers |
可以看到,这份主要分为 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 |
|
可以看到,当系统中@ConditionalOnMissingBean(JdbcOperations.class)
时,它会为我们创建一个JdbcTemplate
,这样我们就可以直接在项目中使用 @Autowired
注入了。
实际上,spring.factories 只是一个普通的 properties文件,里面的内容也是可以随意定制的,只不过SpringBoot用作了自动配置,给我们提供了一个范例。这 7 个部分也仅仅只是 spring-boot-autoconfigure所需要的,很多其它的第三方JAR包中的 spring.factories 会定义很多自己需要的配置。
总结
至此,我们以源码解读的试完整地讲解了整个SpringBoot的启动过程。讲解了如何从@SpringBootApplication
注解找到所有自动配置的类并完成配置过程,也从SpringApplication.run
的执行过程中重现了SpringBoot应用在启动过程中的各个生命周期。也讲解了项目中常见的扩展点,以及如何去扩展我们的应用能力。