spring boot配置加载
spring boot配置加载
SB启动时,会创建出ConfigurableEnvironment,并添加默认属性、命令行属性。
通过回调SpringApplicationRunListener#environmentPrepared发布ApplicationEnvironmentPreparedEvent事件。
SB2.4以后,通过EnvironmentPostProcessorApplicationListener监听ApplicationEnvironmentPreparedEvent事件遍历EnvironmentPostProcessor进行处理配置文件加载。
SB2.4以后,通过ConfigDataEnvironmentPostProcessor进行实际的配置数据加载。
核心逻辑是通过将已经存在的属性源、默认应该导入的位置封装为ConfigDataEnvironmentContributor,最终通过ConfigDataEnvironmentContributors进行加载。
ConfigDataEnvironmentContributor
在2.4版本后,Spring 决定更改加载配置文件背后的逻辑。
为了表示可能对环境做出贡献的元素,引入了 ConfigDataEnvironmentContributor,每个贡献者都将在此过程中被替换。
Spring 决定使用树作为数据结构来处理/应用配置数据。
我将 ConfigDataEnvironmentContributor 称为 CDEContributor。
每个贡献者都可以导入一些属性,并将其作为子项附加,从而创建整个树。
每个贡献者将包含一些描述节点的元数据:
- location:一个 configDataLocation,将使用 configDataLocationResolver 解析为一个或多个资源。
- ressource:每个 propertySource 都会被包装在一个 ConfigData 对象中,ConfigResource 指示可以从哪个 ConfigData 加载。
- **propertySource:**链接到该节点的propertySource。
4.children :子贡献者,如前所述,当贡献者导入某些属性时,将添加新的贡献者作为该贡献者的子代。为每个 importPhase 创建子项。
- **kind:**贡献者有多种类型:
ROOT:我们树的根
INITIAL_IMPORT,这个贡献者被添加在开头,表明贡献者处于活跃状态,应该得到处理
通过包装现有 propertySource 获得的EXISTING Contributors 这些类型的 propertySource 将不会被处理
UNBOUND_IMPORT 已导入但尚未绑定的贡献者
BOUND_IMPORT 从另一个贡献者导入的贡献者
EMPTY_LOCATION 一个不包含任何要加载内容的有效位置。
除此之外,儿童贡献者将被分为两组儿童 BEFORE_PROFILE_ACTIVATION 和 AFTER_PROFILE_ACTIVATION (将其想象为左和右),此信息将用于在遍历树时设置优先级,即在遍历之前首先查看 AFTER_PROFILE_ACTIVATION 领域另一个(或者在本例中先向右然后向左)。
入口
public ConfigurableApplicationContext run(String... args) {
......
/**
* 构造出 ConfigurableEnvironment
*
* 1. 可以使用 ApplicationContextFactory 来生成 ConfigurableEnvironment
* 2. 回调 SpringApplicationRunListener#environmentPrepared 配置 ConfigurableEnvironment
* 3. 修改属性的访问顺序为: 命令行参数 -> 系统属性 -> 环境变量 ... -> 默认属性(默认是空的)
* */
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
......
}
在这一步会完成ConfigurableEnvironment的创建以及配置的加载工作。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
/**
* 创建出 ConfigurableEnvironment
*
* 如果没有默认的,就会读取 META-INF/spring.factories 中key为 `ApplicationContextFactory.class.getName()` 的实例
* 回调 {@link ApplicationContextFactory#create(WebApplicationType)} 方法生成 ConfigurableEnvironment
*
* */
ConfigurableEnvironment environment = getOrCreateEnvironment();
/**
* 配置Environment,其实就是扩展Environment能访问的属性信息
* 访问顺序:命令行参数 -> ... -> 默认属性
*
* 注:默认属性可以这样子进行配置 {@link org.springframework.boot.SpringApplicationTests#defaultCommandLineArgs()}
* */
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 将 ConfigurationPropertySourcesPropertySource 放到第一个位置
ConfigurationPropertySources.attach(environment);
/**
* 回调 {@link SpringApplicationRunListener#environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
* 对 environment 进行配置
* */
listeners.environmentPrepared(bootstrapContext, environment);
// 移动 defaultProperties 到最后,即优先级最低
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(
!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties."
);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
// 转换成 ConfigurableEnvironment 类型的
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
创建Environment
SERVLET环境默认创建的是ApplicationServletEnvironment。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
}
return (environment != null) ? environment : new ApplicationEnvironment();
}
ApplicationServletEnvironment.Factory
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment();
}
配置Environment
为ConfigurableEnvironment添加默认的属性源以及命令行参数。
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
/**
* 扩展 environment 访问的属性
*
* 访问顺序:命令行参数 -> ... -> 默认属性
*
* */
configurePropertySources(environment, args);
// 模板方法,空实现
configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (!CollectionUtils.isEmpty(this.defaultProperties)) {
// 将 defaultProperties 装饰成 propertySource 然后注册到 sources 中(放在最后)
DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
}
// 将 args 构造成 SimpleCommandLinePropertySource 然后注册到 sources 中(放到前面)
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
} else {
// 放到前面
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
回调environmentPrepared
默认只有一个EventPublishingRunListener发布环境预处理事件,将创建的环境对象传递。
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
SB2.4以后通过EnvironmentPostProcessorApplicationListener监听ApplicationEnvironmentPreparedEvent事件,会回调ConfigDataEnvironmentPostProcessor进行配置文件的加载处理。
EnvironmentPostProcessorApplicationListener:
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent();
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
// 回调 EnvironmentPostProcessor
postProcessor.postProcessEnvironment(environment, application);
}
}
配置数据加载
版本:SB2.7.x
配置文件:
# classPath:application.yml
spring:
application:
name: art-server-system
cloud:
nacos:
config:
server-addr: ${NACOS_HOST:art-nacos}:8848
discovery:
server-addr: ${NACOS_HOST:art-nacos}:8848
username: nacos
password: art-nacos
profiles:
active: dev
config:
import:
- optional:nacos:${spring.application.name}.yaml
- optional:nacos:art-common.yaml
ConfigDataEnvironmentPostProcessor处理监听
通过ConfigDataEnvironment进行处理。
ConfigDataEnvironmentPostProcessor:
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
/**
* 找到属性文件(application.yaml|yml|xml|properties),解析成 PropertySource 然后添加到 environment 中(往后面加)
* */
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles)
.processAndApply();
}
catch (UseLegacyConfigProcessingException ex) {
this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
ex.getConfigurationProperty()));
configureAdditionalProfiles(environment, additionalProfiles);
postProcessUsingLegacyApplicationListener(environment, resourceLoader);
}
}
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles, this.environmentUpdateListener);
}
ConfigDataEnvironment初始化
ConfigDataEnvironment的构造中初始化了一些要用到的成员变量。同时将已有的属性源以及需要导入配置的位置 封装成 Contributors。
ConfigDataEnvironment:
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles, ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class).orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
// 解析需要加载配置位置的 读取 spring.factories 文件中 key 为 ConfigDataLocationResolver 的
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener : ConfigDataEnvironmentUpdateListener.NONE;
// 实际加载配置数据的 通过读取 spring.factories 文件中 key 为 ConfigDataLoader 的
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
// 将已有的属性源以及需要导入配置的位置 封装成 Contributors
this.contributors = createContributors(binder);
}
- 将环境中已有的属性源封装成ConfigDataEnvironmentContributor, kind为Existing。
- 添加初始化属性值 将默认的几个位置封装成ConfigDataEnvironmentContributor ,kind为INITIAL_IMPORT。
ConfigDataEnvironment:
static {
List<ConfigDataLocation> locations = new ArrayList<>();
// optional: 是固定前缀, ConfigDataLocation.of 会截掉
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}
// 将环境中的propertySources、默认的几个位置封装成ConfigDataEnvironmentContributor
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
MutablePropertySources propertySources = this.environment.getPropertySources();
// 将环境中已有的属性源封装成ConfigDataEnvironmentContributor kind为Existing
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
PropertySource<?> defaultPropertySource = null;
for (PropertySource<?> propertySource : propertySources) {
if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
// 记录 defaultPropertySource
defaultPropertySource = propertySource;
} else {
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'", propertySource.getName()));
// 将 propertySource 装饰成 ConfigDataEnvironmentContributor
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
// 添加初始化属性值 将默认的几个位置封装成ConfigDataEnvironmentContributor
contributors.addAll(getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return createContributors(contributors);
}
// 封装默认的几个位置
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
// 获取 "spring.config.import" 配置的路径,装饰成 ConfigDataEnvironmentContributor
addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
// 获取 "spring.config.additional-location" 配置的路径,装饰成 ConfigDataEnvironmentContributor
addInitialImportContributors(initialContributors, bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
/**
* 获取 "spring.config.location" 配置的路径,装饰成 ConfigDataEnvironmentContributor
*
* 默认是
* classpath:/
* classpath:/config/
* file:./
* file:./config/
* file:./config/'*'/
* */
addInitialImportContributors(initialContributors, bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
// 将默认的几个位置封装成ConfigDataEnvironmentContributor 类型为INITIAL_IMPORT 后面导入
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors, ConfigDataLocation[] locations) {
for (int i = locations.length - 1; i >= 0; i--) {
initialContributors.add(createInitialImportContributor(locations[i]));
}
}
private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
return ConfigDataEnvironmentContributor.ofInitialImport(location);
}
static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) {
List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, false, null, null, properties,
null, null);
}
可以看到系统属性、环境变量、命令行属性等已有的属性源,kind为Existing。而其他几个需要导入的位置,kind被封装为了INITIAL_IMPORT。
processAndApply
处理所有贡献,并将任何新导入的属性源应用于Environment。
void processAndApply() {
// importer 是一个很关键的对象,包含了扫描的目录、扫描到的文件 当然这里只是进行了实例化,还没有任何数据
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
/**
* 处理 contributors 的初始化,其实就是加载并解析 contributors 路径下存在的属性文件
* 其目的是找到所有可能的属性文件(比如 application.yml)
* */
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
// 处理没有 profile
contributors = processWithoutProfiles(contributors, importer, activationContext);
// 拿到 profile
activationContext = withProfiles(contributors, activationContext);
/**
* 根据 profile 的名字,找到属性文件
* */
contributors = processWithProfiles(contributors, importer, activationContext);
// 将最终的结果设置到 environment 中,也就是属性文件生效了
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
}
processInitial
这一波主要处理默认的那几个位置的导入。也就是kind为imital_import类型的。主要看8、9的处理。
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors, ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");
// 处理配置的导入
contributors = contributors.withProcessedImports(importer, null);
registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);
return contributors;
}
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processed = 0;
while (true) {
// 获取待解析的 contributor
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
// 说明都解析完了,返回解析的结果
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
// 更新一下kind 不处理
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
// 是用来拼接路径的,判断拼接完的路径下的文件是否存在
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
/**
* 解析并且读取配置文件
*
* 思路就是:
* 1. imports的是路径 就拼接上 application 然后判断资源是否存在
* 2. imports是文件 判断资源是否存在
* 3. 存在就解析文件成 PropertySource
*
* 主要是会使用这几个类。读取 META-INF/spring.factories 得到的:
* - org.springframework.boot.context.config.ConfigDataLocationResolver
* 是用来判断指定路径下是否存在属性文件,文件允许啥后缀和怎么解析是依赖于 PropertySourceLoader
*
* - org.springframework.boot.env.PropertySourceLoader
* 这是用来允许哪些后缀的属性文件和解析属性文件成 PropertiesSource 的
*
* - org.springframework.boot.context.config.ConfigDataLoader
* 是用来处理 ConfigDataLocationResolver 的解析结果
* */
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
// 加载完了 把加载的结果封装到子节点 并更新当前节点
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
// 替换当前节点
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
获取到第一个需要处理的贡献者。可以看到它import的路径为项目根路径下,根路径下没有配置文件,所以这一波肯定加载不到。
解析并且加载配置。
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<ConfigDataLocation> locations) {
try {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
// 解析加载路径
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
// 读取文件内容
return load(loaderContext, resolved);
} catch (IOException ex) {
throw new IllegalStateException("IO error on loading imports from " + locations, ex);
}
}
可以看到第一个解析出的位置为项目根路径下的application.yml。
可以看到,对于application.yml的加载,使用的是StandardConfigDataLoader。
但是由于我们项目的根目录下并没有该文件,所以加载的结果为空。
当前贡献者解析并加载完成之后,会更新当前贡献者的子节点,并更新当前贡献者的信息。
根据加载结果构造出的子节点如下:
当前贡献者更新后内容如下:
继续迭代下一个贡献者。
可以看到此时上一轮的贡献者已经被更新:
获取下一个需要处理的贡献者:
解析需要加载的位置,可以看到解析出了两个classpath下的文件路径:
加载类路径下的application.yml
可以看到,这一轮加载有两个结果集,其中一个不为空:
和上一轮加载一样,更新当前贡献者,可以看到,其中一个kind为unbound_import:
和之前一样,更新当前贡献者的信息,进入下一轮加载。
可以看到这一轮加载中获取到的是我们上一轮加载到的application.yml。但是unbound_import类型的在这里只是更新了kind的状态。
继续迭代,处理上一轮更新状态的application.yml:
可以看到,这里解析出的位置是空,因为此时的profiles为空,NacosConfigDataLocationResolver直接返回了空集合,表示当前不加载。
至此,processInitial的加载完成。
此时result中的结构,可以看到8只有一个为空的子节点。
9中有两个子节点,一个为空,一个不为空(classpath:application.yml)
创建激活上下文
processWithoutProfiles
由于此时Profiles为空,这个方法没有进行任何处理。
withProfiles
processWithProfiles
可以看到phase已经变化。
获取第一个要处理的贡献者,对应的加载位置是项目根目录。
依然加载不到,因为我们在项目根目录下没配置。
更新当前节点及其子节点。
导入下一个贡献者。
可以看到这一轮解析出了我们nacos的配置路径。
已经加载到了nacos中的配置信息。
更新当前节点以及子节点。
迭代下一个贡献者。
更新kind。
同上。
结束加载。
applyToEnvironment
将加载到的配置应用到环境。