引言
SpringBoot以其简化的配置和强大的开箱即用功能而备受欢迎,而配置文件的加载是Spring Boot应用启动过程中的关键步骤之一。深入理解Spring Boot启动时如何加载配置文件的源码,有助于开发者更好地理解其内部工作原理,提高配置管理的灵活性和可维护性。本文将从源码入手,解读Spring Boot启动时配置文件加载的关键组件和步骤。
本文使用的SpringBoot版本为:2.7.0
SpringBoot使用事件监听的方式去读取配置文件。在SpringBoot2.4.0以前是通过ConfigFileApplicationListener
去监听读取配置文件的事件,在SpringBoot2.4.0时废弃了ConfigFileApplicationListener
,该使用EnvironmentPostProcessorApplicationListener
去监听文件读取事件。这点区别需要注意。
本文只会解读配置文件加载步骤,除加载文件步骤以外的会忽略。
加载配置文件大致流程如下:
准备应用程序环境
- 在SpringBootApplication执行run方法后,获取程序中的事件监听器后,执行
prepareEnvironment
方法开始准备环境。
- 通知注册的监听器,应用程序的环境已经准备好。
-
doWithListeners
遍历注册的监听器,对每个监听器执行environmentPrepared
操作,通知它们应用程序的环境已经准备好。 -
处理应用事件的组件
SimpleApplicationEventMulticaster
开始执行multicastEvent
去广播环境准备事件ApplicationEnvironmentPreparedEvent
给注册的监听器,环境已经准备好,让他们可以执行环境准备阶段的自定义逻辑。
至此,应用程序环境已经准备好的事件已经广播出去,接下来EnvironmentPostProcessorApplicationListener
监听器监听到事件后就可以开始处理读取配置文件的逻辑。
准备加载配置文件环境
EnvironmentPostProcessorApplicationListener
监听到ApplicationEnvironmentPreparedEvent
即环境已经准备好的事件,开始处理执行实现EnvironmentPostProcessor
类的postProcessEnvironment
方法。
而注册EnvironmentPostProcessor
实现类的有7个。都是加载类路径下的META-INF/spring.factories
文件中配置的EnvironmentPostProcessor
实现类。
而ConfigDataEnvironmentPostProcessor
就是用于处理加载配置文件的实现类。同时这里也是SpringBoot2.4.0前后版本关于加载配置文件差异的一个地方。
ConfigDataEnvironmentPostProcessor
开始执行postProcessEnvironment
方法,创建ConfigDataEnvironment
实例,然后执行其processAndApply
。
ConfigDataEnvironment
类在SpringBoot2.4.0版本引入,它是SpringBoot配置数据加载和管理的核心组件。它负责从多个源加载、解析和处理配置数据,并将这些数据整合到应用环境中。在这里主要去创建ConfigDataLocationResolvers
,ConfigDataLoaders
以及ConfigDataEnvironmentContributors
。
ConfigDataLocationResolvers
在SpringBoot2.4.0版本中引入,是负责解析和定位配置数据源位置的一个组件集合。是一个工厂类,用于创建配置数据位置解析器的实例,它包含了一组ConfigDataLocationResolver
的实现类,目前只有两个实现类:StandardConfigDataLocationResolver
以及ConfigTreeConfigDataLocationResolver
。
其中ConfigTreeConfigDataLocationResolver
主要用于解析Config Tree 类型的配置数据位置。Config Tree是SpringBoot2.4.0引入的一种配置数据存储格式,可以将配置文件以树形结构组织,使得配置文件之间的关系更加清晰。 而StandardConfigDataLocationResolver
它用于解析标准的配置数据位置,即 SpringBoot2.4之前版本中使用的传统配置文件存放方式。这种方式通常是将配置文件放在类路径下的config
目录中,或者在文件系统的特定位置,例如我们常写的applicaiton.properties
或者applicaiton.yml
。这个类也是本文用于加载配置文件的解析器。
ConfigDataLoaders
是SpringBoot中处理配置数据加载的组件,也是SpringBoot2.4.0引入。它是一个工厂类,用于创建配置数据加载器的实例,配置数据加载器均实现ConfigDataLoader
接口。在SpringBoot中,配置数据加载器负责实际加载配置数据,将配置文件的内容解析成应用程序可用的配置信息。同样的他也只有两个实现类:ConfigTreeConfigDataLoader
和StandardConfigDataLoader
。 同解析器,ConfigTreeConfigDataLoader
主要用于加载Config Tree类型的配置数据。而StandardConfigDataLoader
用于加载标准的配置数据。
ConfigDataEnvironmentContributors
是SpringBoot中用于管理配置数据环境贡献者的组件。它的主要作用是维护一组贡献者,这些贡献者负责提供配置数据的加载、处理和管理。同样也是SpringBoot 2.4.0之后引入。它从特定的源或根据特定规则加载并解析配置数据,然后将解析后的结果(通常是以PropertySource
形式)添加到ConfigDataEnvironment
对象中。并且负责按照预定义的顺序和优先级策略来加载和合并不同来源的配置信息,确保正确地覆盖和合并属性值。不同的ConfigDataEnvironmentContributor
可以响应不同的环境变量、系统属性或激活的profile,从而动态地调整加载哪些配置数据。
此时他的工作是获取与给定源相关联的Binder
,用于对配置数据进行绑定操作。执行getInitialImportContributors(binder)
方法获取初始导入的配置数据贡献者,加入到贡献者列表中。
这个方法就很重要了,这里就是SpringBoot加载的文件的默认位置以及加载文件的顺序。执行第1个方法时,可以通过IMPORT_PROPERTY
即spring.config.import
这个值可以指定要导入的额外配置数据位置,这些位置将会在配置数据加载时被导入。SpringBoot将会使用该属性指定的位置作为主要的配置数据来源,并将其导入到应用程序的配置中。这个属性通常用于指定一个主要的配置文件,覆盖默认的配置文件位置。它的优先级也是最高的。例如:
properties
spring.config.import=classpath:/imported-config/application.yml
执行第2个方法即绑定ADDITIONAL_LOCATION_PROPERTY
指定的目录spring.config.additional-location
,这个目录用于指定额外的配置数据导入位置。指定的额外导入位置会在主要位置之外被考虑。这个属性用于添加额外的配置数据位置,可以与主要位置一起使用,而不是替代它。
然后就是第3个方法,绑定LOCATION_PROPERTY
指定的目录spring.config.location
。该目录作为主要的配置数据位置。可以通过设置该属性来指定主要的配置数据位置,这个位置会被优先考虑,覆盖默认的位置。而默认位置有如下:
text
optional:classpath:/
optional:classpath:/config/
optional:file:./
optional:file:./config/
optional:file:./config/*/
其中optional
代表可选的配置文件位置。由上述addInitialImportContributors
可以看出默认配置文件加载顺序是从上到下优先级越来越高。即加载顺序为:
text
file:./
file:./config/
file:./config/*/
classpath:/
classpath:/config/
这个加载顺序不同SrpingBoot2.4.0于以前的版本。
这里注意这个是加载文件的顺序,而不是加载读取配置的顺序。加载读取配置的顺序请往下看
其中file:./
、file:./config/
、file:./config/*/**
都是在文件系统中搜索配置文件,这种方式适用于需要在文件系统上动态配置文件的场景,其中 *
可以匹配任意子目录。 而classpath:/
、classpath:/config/
则表示在类路径(classpath)下搜索配置文件,包括根路径和 /config/
子路径。这种方式适用于将配置文件打包在应用程序的 JAR 文件中或者放在类路径下的config
目录中。
Contributors翻译为贡献者,类似给配置数据环境提供数据的加载,处理和管理的。
到这里ConfigDataEnvironment
中关于加载和解析配置文件的部分就准备好了,接下来就开始执行processAndApply
方法开始加载解析文件。
配置文件加载解析
- 创建配置文件导入器
ConfigDataImporter
,将ConfigDataEnvironment
中的加载器以及解析器都放入导入器中。 - 执行
processInitial
方法,然后配置数据贡献者ConfigDataEnvironmentContributors
开始执行withProcessedImports
方法开始执行加载以及解析配置文件。循环配置数据贡献者直至拿到所有的配置文件。
-
执行创建
ConfigDataLocationResolverContext
解析器上下文,ConfigDataLoaderContext
加载器上下文,以及获取贡献者中配置文件的配置。 -
配置文件导入器执行
resolveAndLoad
方法开始执行解析和加载数据。这方法中分为resolve
解析以及load
加载数据。 -
resolve
会调用ConfigDataEnvironment
中创建的解析器,比如:StandardConfigDataLocationResolver
,然后去解析ConfigDataEnvironmentContributor
中保存的配置文件的路径。StandardConfigDataLocationResolver
会先把路径拿出来按照;
进行拆分 (方法在ConfigDataLocation
中),然后组装每个路径下的文件位置信息,对于文件名默认都为application
,对于文件类型,需要使用到PropertySourceLoader
他有两个子类:YamlPropertySourceLoader
和PropertiesPropertySourceLoader
,通过getFileExtensions
方法获取文件类型。其中PropertiesPropertySourceLoader
对应properties
和xml
,而YamlPropertySourceLoader
对应yml
以及yaml
。
这样就可以用ConfigDataLocation
路径+applicaiton+getFileExtensions得到一组文件路径。
然后解析器会检查这些文件是否存在,如果不存在的则会过滤掉。
最后将找到的文件放入StandardConfigDataResource
集合中返回,由load
方法去加载数据。
load
方法将resolve
方法解析出来的文件路径,有对应的加载器去文件中将数据取出来。 这里需要注意的是,读取文件是从最后一个开始读取,即跟文件加载顺序相反,所以配置加载顺序为:
text
file:./config/*/
file:./config/
file:./
classpath:/config/
classpath:/
此时的loaders.load即在ConfigDataEnvironment
中创建的加载器,本文中使用StandardConfigDataLoader
进行加载,然后在配置加载器中由文件类型对应的加载器进行数据加载。
将读取的数据封装到ConfigData
当中返回。最后将数据组装到ConfigDataEnvironmentContributors
中,最后把数据放入当前应用环境中。这样SpringBoot启动时读取文件的流程就结束了。当然后面还有按照当前指定环境profiles
读取,但读取流程一致。只要是配置的优先级,这个我们放在下一篇文章中继续解读。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等