当前位置: 首页 > news >正文

小架构step系列23:加载自定义配置

1 概述

在业务系统中,自定义配置是比较多的,Spring提供了一套加载配置文件的机制,了解其原理,可以帮助解决一些配置文件的位置问题,更好地使用这些配置文件。

2 原理

2.1 文件路径组装

Spring提供了一套默认的文件路径,但也支持自定义扩展,甚至替换掉原有的路径机制。上文已经了解到自定义的配置文件加载是从ConfigDataEnvironmentPostProcessor的postProcessEnvironment()开始的:

// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {// 1. 调用内部私有方法后处理Environment,参数增加Profile信息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();// 2. 获取ConfigDataEnvironment,并调用其processAndApply()方法getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();}catch (UseLegacyConfigProcessingException ex) {// 省略部分代码}
}
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {// 3. 创建ConfigDataEnvironment对象,里面包含environment环境对象等信息return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles, this.environmentUpdateListener);
}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironment
class ConfigDataEnvironment {// 4. 初始化默认的配置文件路径,一类是在classpath路径下的,一类是在当前目录下的static {List<ConfigDataLocation> locations = new ArrayList<>();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]);}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;this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);this.additionalProfiles = additionalProfiles;this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener : ConfigDataEnvironmentUpdateListener.NONE;this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());// 5. 创建Contributorthis.contributors = createContributors(binder);}// 省略部分代码
}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironment
private ConfigDataEnvironmentContributors createContributors(Binder binder) {this.logger.trace("Building config data environment contributors");MutablePropertySources propertySources = this.environment.getPropertySources();List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);PropertySource<?> defaultPropertySource = null;// 6. 遍历propertySources,创建对应的Contributor//    propertySources,创建对应的里面放的是前面加载的系统属性、系统环境变量、命令行等配置资源对象,每个资源对象也创建一个对应的Contributor//    这次主要是关注自定义的配置文件,这块可以忽略for (PropertySource<?> propertySource : propertySources) {if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {defaultPropertySource = propertySource;}else {this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'", propertySource.getName()));contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));}}// 7. 获取可能的自定义配置文件,封装到Contributorcontributors.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);
}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironment
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();// 8. 从系统属性、系统环境变量、命令行等配置资源找spring.config.import,如果配置了spring.config.import则创建对应的Contributor存放配置文件路径//    IMPORT_PROPERTY = "spring.config.import",可配置具体的配置文件路径,也可以配置目录路径//    在bindLocations里传了一个空的EMPTY_LOCATIONS=new ConfigDataLocation[0],也就是如果没有找到,则不创建ContributoraddInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));// 9. 同样找spring.config.additional-location是否有配置,如果配置了则创建对应的Contributor存放配置文件路径,如果没有配置则不创建Contributor//    ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"addInitialImportContributors(initialContributors, bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));// 10. 同样找spring.config.location,如果配置了则创建对应的Contributor存放配置文件路径,如果没有配置则用默认的//     这个比较重要,默认是在classpath和jar包当前目录下找,但如果配置了这个属性,则替换掉了默认的配置//     LOCATION_PROPERTY = "spring.config.location"addInitialImportContributors(initialContributors, bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));return initialContributors;
}
private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {// binder.bind()的过程主要是把propertyName当key,从环境对象里找对应的配置,也就是主要从系统属性、系统环境变量、命令行参数里找是否有配置// 如果没有配置,则看默认的other里的路径return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
}// 回到ConfigDataEnvironmentPostProcessor的postProcessEnvironment(),调用ConfigDataEnvironment的.processAndApply()
// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor
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();// 2. 获取ConfigDataEnvironmentgetConfigDataEnvironment(environment, resourceLoader, additionalProfiles)// 11. 调用ConfigDataEnvironment的processAndApply()方法,注意前面创建的Contributor存放在ConfigDataEnvironment对象里.processAndApply();}catch (UseLegacyConfigProcessingException ex) {// 省略部分代码}
}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironment
void processAndApply() {// 12. 创建ConfigDataImporter导入器,由它来对配置文件进行加载ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);// 13. 处理ContributorConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);ConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));contributors = processWithoutProfiles(contributors, importer, activationContext);activationContext = withProfiles(contributors, activationContext);contributors = processWithProfiles(contributors, importer, activationContext);applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
}
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors, ConfigDataImporter importer) {this.logger.trace("Processing initial config data environment contributors without activation context");// 14. 处理加载配置文件,contributors为ConfigDataEnvironmentContributors,里面包含了前面创建的所有Contributorcontributors = contributors.withProcessedImports(importer, null);registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);return contributors;
}// 源码位置:org.springframework.boot.context.config.ConfigDataEnvironmentContributors
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) {// 15. 循环遍历所有Contributor,获取下一个Contributor,这里只关注自定义配置文件的ContributorConfigDataEnvironmentContributor 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) {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);// 16. 取出Contributor里的存储的配置文件路径,比如classpath:/、file:/等List<ConfigDataLocation> imports = contributor.getImports();this.logger.trace(LogMessage.format("Processing imports %s", imports));// 17. 使用导入器importer加载配置文件,importer为ConfigDataImporterMap<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++;}
}// 源码位置:org.springframework.boot.context.config.ConfigDataImporter
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,List<ConfigDataLocation> locations) {try {// 18. 如果启动参数里指定了profile:-Dspring.profiles.active=xxx,则profile会影响配置文件的加载Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;// 19. 组装配置文件的路径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);}
}
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext, Profiles profiles, List<ConfigDataLocation> locations) {List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());for (ConfigDataLocation location : locations) {// 20. 遍历每个配置文件路径,进行配置文件路径处理resolved.addAll(resolve(locationResolverContext, profiles, location));}return Collections.unmodifiableList(resolved);
}
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext, Profiles profiles, ConfigDataLocation location) {try {// 21. 使用Resolver处理文件路径,resolvers为ConfigDataLocationResolvers,是对多个Resolver的封装return this.resolvers.resolve(locationResolverContext, location, profiles);}catch (ConfigDataNotFoundException ex) {handle(ex, location, null);return Collections.emptyList();}
}// 源码位置:org.springframework.boot.context.config.ConfigDataLocationResolvers
List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {if (location == null) {return Collections.emptyList();}// 22. 遍历所有Resolver进行文件路径处理//     Resolver有两种,这里只关注第二种比较常用的StandardConfigDataLocationResolver://     org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver//     org.springframework.boot.context.config.StandardConfigDataLocationResolverfor (ConfigDataLocationResolver<?> resolver : getResolvers()) {if (resolver.isResolvable(context, location)) {return resolve(resolver, context, location, profiles);}}throw new UnsupportedConfigDataLocationException(location);
}
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver, ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {// 23. resolver的处理封装到了一个lambda表达式中List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));if (profiles == null) {return resolved;}List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,() -> resolver.resolveProfileSpecific(context, location, profiles));return merge(resolved, profileSpecific);
}
private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific, Supplier<List<? extends ConfigDataResource>> resolveAction) {// 24. resolveAction.get()触发lambda表达式执行List<ConfigDataResource> resources = nonNullList(resolveAction.get());List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());for (ConfigDataResource resource : resources) {resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));}return resolved;
}// 源码位置:org.springframework.boot.context.config.StandardConfigDataLocationResolver
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) throws ConfigDataNotFoundException {// 25. 调用内部方法处理配置文件路径,路径可以用分号分隔,即在一个字符串中配置多个路径或文件return resolve(getReferences(context, location.split()));
}
// 源码位置:org.springframework.boot.context.config.ConfigDataLocation
public ConfigDataLocation[] split() {return split(";");
}// 源码位置:org.springframework.boot.context.config.StandardConfigDataLocationResolver
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation[] configDataLocations) {Set<StandardConfigDataReference> references = new LinkedHashSet<>();for (ConfigDataLocation configDataLocation : configDataLocations) {// 26. 循环处理每个路径references.addAll(getReferences(context, configDataLocation));}return references;
}
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation configDataLocation) {String resourceLocation = getResourceLocation(context, configDataLocation);try {// 27. 如果配的是目录,则按目录处理,否则按文件处理if (isDirectory(resourceLocation)) {return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE);}return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE);}catch (RuntimeException ex) {throw new IllegalStateException("Unable to load config data from '" + configDataLocation + "'", ex);}
}
private Set<StandardConfigDataReference> getReferencesForDirectory(ConfigDataLocation configDataLocation, String directory, String profile) {Set<StandardConfigDataReference> references = new LinkedHashSet<>();// 28. 处理目录//     configNames是在StandardConfigDataLocationResolver初始化构建的,先从环境对象里取spring.config.name配置(可用逗号分隔多个),//     如果没有配置spring.config.name,则取默认值DEFAULT_CONFIG_NAMES = { "application" }for (String name : this.configNames) {Deque<StandardConfigDataReference> referencesForName = getReferencesForConfigName(name, configDataLocation, directory, profile);references.addAll(referencesForName);}return references;
}
private Deque<StandardConfigDataReference> getReferencesForConfigName(String name, ConfigDataLocation configDataLocation, String directory, String profile) {Deque<StandardConfigDataReference> references = new ArrayDeque<>();// 29. 遍历资源加载器加载配置文件//     资源加载器有两种,一种是.properties/.xml文件的加载器,另外一种是.yml/.yaml文件的加载器://       org.springframework.boot.env.PropertiesPropertySourceLoader//       org.springframework.boot.env.YamlPropertySourceLoaderfor (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {for (String extension : propertySourceLoader.getFileExtensions()) {// 30. 组装配置文件的路径、文件名、文件后缀,放到一个ArrayDeque里返回//     directory + name表示文件目录和文件名称先拼接起来,假定目录是classpath:/,文件名为application,那拼接起来就是classpath:/applicationStandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, directory,directory + name, profile, extension, propertySourceLoader);if (!references.contains(reference)) {references.addFirst(reference);}}}return references;
}// 源码位置:org.springframework.boot.context.config.StandardConfigDataReference
StandardConfigDataReference(ConfigDataLocation configDataLocation, String directory, String root, String profile,String extension, PropertySourceLoader propertySourceLoader) {this.configDataLocation = configDataLocation;String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";// 31. 配置文件路径构成:文件路径+文件名称+profile后缀(配了才有)+后缀//     假设使用的都是默认的,那么文件路径就是classpath:/等四种,文件名为application,后缀有.properties/.xml/.yml/.yaml四种//     拼接出的文件路径有以下几种://       file:./application.yaml//       file:./application.yml//       file:./application.xml//       file:./application.properties//       file:./config/application.yaml//       file:./config/application.yml//       file:./config/application.xml//       file:./config/application.properties//       file:./config/*/application.yaml//       file:./config/*/application.yml//       file:./config/*/application.xml//       file:./config/*/application.properties//       classpath:/application.yaml//       classpath:/application.yml//       classpath:/application.xml//       classpath:/application.properties//       classpath:/config/application.yaml//       classpath:/config/application.yml//       classpath:/config/application.xml//       classpath:/config/application.propertiesthis.resourceLocation = root + profileSuffix + ((extension != null) ? "." + extension : "");this.directory = directory;this.profile = profile;this.propertySourceLoader = propertySourceLoader;
}
从上面可以看到提供的默认路径有:
  • optional:classpath:/
  • optional:classpath:/config/
  • optional:file:./
  • optional:file:./config/
  • optional:file:./config/*/
支持的文件类型有:
  • .properties
  • .xml
  • .yml
  • .yaml
支持的文件名有:
application
其中,路径可以通过配置spring.config.location(用分号分隔配多个)来替换掉,文件名可以通过spring.config.name(用逗号分隔配多个)来替换掉,文件类型倒是跟代码强相关没有提供替换的方式,此外还可以指定profile来过滤。这三者都可能有多个,经它们排列组合从而得到一个配置文件的全集,只要这些文件存在就会被加载。

2.2 配置文件读取

上面只是组装了文件路径,没有真正读取文件,继续读文件内容:
// 回到ConfigDataImporter的resolveAndLoad
// 源码位置:org.springframework.boot.context.config.ConfigDataImporter
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,List<ConfigDataLocation> locations) {try {// 18. 如果启动参数里指定了profile:-Dspring.profiles.active=xxx,则profile会影响配置文件的加载Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;// 19. 组装配置文件的路径List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);// 32. 读取配置文件内容return load(loaderContext, resolved);}catch (IOException ex) {throw new IllegalStateException("IO error on loading imports from " + locations, ex);}
}// 源码位置:org.springframework.boot.context.config.ConfigDataImporter
private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext, List<ConfigDataResolutionResult> candidates) throws IOException {Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();for (int i = candidates.size() - 1; i >= 0; i--) {ConfigDataResolutionResult candidate = candidates.get(i);ConfigDataLocation location = candidate.getLocation();ConfigDataResource resource = candidate.getResource();this.logger.trace(LogMessage.format("Considering resource %s from location %s", resource, location));if (resource.isOptional()) {this.optionalLocations.add(location);}if (this.loaded.contains(resource)) {this.logger.trace(LogMessage.format("Already loaded resource %s ignoring location %s", resource, location));this.loadedLocations.add(location);} else {try {// 33. 使用资源加载器loaders去读取文件,loaders为ConfigDataLoaders,是对多个loader的封装ConfigData loaded = this.loaders.load(loaderContext, resource);if (loaded != null) {this.logger.trace(LogMessage.format("Loaded resource %s from location %s", resource, location));this.loaded.add(resource);this.loadedLocations.add(location);result.put(candidate, loaded);}}catch (ConfigDataNotFoundException ex) {handle(ex, location, resource);}}}return Collections.unmodifiableMap(result);
}// 源码位置:org.springframework.boot.context.config.ConfigDataLoaders
<R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {// 34. 匹配配置文件对应的loader,这里主要关注StandardConfigDataLoader//     org.springframework.boot.context.config.ConfigTreeConfigDataLoader//     org.springframework.boot.context.config.StandardConfigDataLoaderConfigDataLoader<R> loader = getLoader(context, resource);this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName()));// 35. 用loader加载文件return loader.load(context, resource);
}// 源码位置:org.springframework.boot.context.config.StandardConfigDataLoader
public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource) throws IOException, ConfigDataNotFoundException {if (resource.isEmptyDirectory()) {return ConfigData.EMPTY;}ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());StandardConfigDataReference reference = resource.getReference();Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(), Origin.from(reference.getConfigDataLocation()));String name = String.format("Config resource '%s' via location '%s'", resource, reference.getConfigDataLocation());// 36. 根据配置文件的类型读取文件内容//     PropertySourceLoader资源加载器有两种,一种是.properties/.xml文件的加载器,另外一种是.yml/.yaml文件的加载器://       org.springframework.boot.env.PropertiesPropertySourceLoader//       org.springframework.boot.env.YamlPropertySourceLoaderList<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;return new ConfigData(propertySources, options);
}// 源码位置:org.springframework.boot.env.PropertiesPropertySourceLoader
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {// 37. 读取properties或xml文件List<Map<String, ?>> properties = loadProperties(resource);if (properties.isEmpty()) {return Collections.emptyList();}List<PropertySource<?>> propertySources = new ArrayList<>(properties.size());for (int i = 0; i < properties.size(); i++) {String documentNumber = (properties.size() != 1) ? " (document #" + i + ")" : "";propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,Collections.unmodifiableMap(properties.get(i)), true));}return propertySources;
}
private List<Map<String, ?>> loadProperties(Resource resource) throws IOException {String filename = resource.getFilename();List<Map<String, ?>> result = new ArrayList<>();if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {// 读xml文件,XML_FILE_EXTENSION = ".xml"result.add((Map) PropertiesLoaderUtils.loadProperties(resource));} else {// 读properties文件List<Document> documents = new OriginTrackedPropertiesLoader(resource).load();documents.forEach((document) -> result.add(document.asMap()));}return result;
}// 源码位置:org.springframework.boot.env.YamlPropertySourceLoader
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", getClass().getClassLoader())) {throw new IllegalStateException("Attempted to load " + name + " but snakeyaml was not found on the classpath");}// 38. 读取yml/yaml文件List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();if (loaded.isEmpty()) {return Collections.emptyList();}List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());for (int i = 0; i < loaded.size(); i++) {String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber, Collections.unmodifiableMap(loaded.get(i)), true));}return propertySources;
}

3 架构一小步

(1) 配置文件放在classpath下,会被打包到jar/war包里,配置文件如果密码之类的信息可能容易泄露。部署到生产环境的时候,配置信息很多都需要修改的,放到jar/war包里如果要修改还得重新打包,比较麻烦,所以配置文件不宜使用jar/war包里的。这个时候可以在启动参数里配置:
  • --spring.config.location:用来替换掉默认的查找路径,可以用分号分隔配多个路径;
  • --spring.config.additional-location:增加额外的查找路径,不会替换默认的查找路径;
(2) 配置文件名默认为application,后缀可以为.properties/.xml/.yml/.yaml,如果文件名需要更换或者增加,可以通过在启动参数指定--spring.config.name=xxx,yyyy(用逗号分隔多个);
(3) 对于开发环境,可以增加profile来特殊指定配置文件的加载:-Dspring.profiles.active=xxx
http://www.lryc.cn/news/597228.html

相关文章:

  • 基于 XGBoost 与 SHAP 的医疗自动化办公与可视化系统(上)
  • 快速梳理遗留项目
  • AI聊天方案:vue+nodeJs+SSE
  • Git 常用的提交类型
  • NX741NX777美光固态闪存NX783NX791
  • CentOS 7 Linux 基础知识点汇总
  • Day01_C++
  • 河南萌新联赛2025第二场-河南农业大学
  • 第九讲:C++中的list与forward_list
  • (进阶向)Python第十三期,opencv的图像预处理方法[1]
  • 性能测试-jmeter实战5
  • 28. 探秘重写与重载:面向对象基础
  • ubuntulinux快捷键
  • Ubuntu 1804 编译ffmpeg qsv MediaSDK libva 遇到的问题记录
  • freqtrade在docker运行一个dryrun实例
  • Python实战:基于Streamlit的股票筛选系统,实时K线图+数据缓存优化
  • C/C++中的内存管理
  • uniapp+vue3小程序点击保存图片、保存二维码
  • 一洽客服系统:小程序接入功能说明
  • 优化:Toc小程序猜你喜欢功能
  • 校园后勤服务平台小程序的设计与实现
  • FastGPT + Kymo:解锁企业专属知识库与智能体开发新体验
  • 【黑马SpringCloud微服务开发与实战】(六)分布式事务
  • Leetcode力扣解题记录--第54题(矩阵螺旋)
  • 算法:数组part01:704. 二分查找 +977.有序数组的平方
  • Java开发岗面试记录合集
  • LLM 中的 温度怎么控制随机性的?
  • AI驱动攻防升级,API安全走到关键档口
  • CentOS 7 Linux 用 yum 安装 Docker,含 Docker 镜像无法拉取问题(即 docker pull 失败)的解决方案
  • 路由器与交换机的区别