spring boot配置加载

fxz大约 27 分钟

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。

每个贡献者都可以导入一些属性,并将其作为子项附加,从而创建整个树。

每个贡献者将包含一些描述节点的元数据:

  1. location:一个 configDataLocation,将使用 configDataLocationResolver 解析为一个或多个资源。
  2. ressource:每个 propertySource 都会被包装在一个 ConfigData 对象中,ConfigResource 指示可以从哪个 ConfigData 加载。
  3. **propertySource:**链接到该节点的propertySource。

4.children :贡献者,如前所述,当贡献者导入某些属性时,将添加新的贡献者作为该贡献者的子代。为每个 importPhase 创建子项。

  1. **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进行处理。


ConfigDataEnvironmentPostProcessorpublic 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。


ConfigDataEnvironmentConfigDataEnvironment(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

将加载到的配置应用到环境。