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

Spring原理分析--获取Environment资源对象

1.使用getEnvironment()获取环境信息

ApplicationContext接口继承了EnvironmentCapable接口,可以通过getEnvironment()获取Environment配置信息,例如:

@SpringBootApplication
public class A01 {public static void main(String[] args) throws IOException {ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);// 获取系统配置及自定义配置信息System.out.println(context.getEnvironment().getProperty("java_home"));System.out.println(context.getEnvironment().getProperty("server.port"));}
}

接下来我们就探究下SpringBoot是如何加载环境及配置信息的

2.Spring读取环境信息原理

2.1 创建Environment对象

SpringBoot启动时就会调用prepareEnvironment创建Environment对象,SpringApplication#run源码如下:

public ConfigurableApplicationContext run(String... args) {// 省略其他代码...// 创建Environment对象,其中包括系统环境信息及Spring配置文件信息ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();context.setApplicationStartup(this.applicationStartup);// 将一些对象设置到容器中,其中包含Environment对象prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);afterRefresh(context, applicationArguments);// 省略其他代码...return context;
}

进入prepareEnvironment方法中创建Environment对象的源码如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {// 1.创建ConfigurableEnvironment对象ConfigurableEnvironment environment = this.getOrCreateEnvironment();// 补充启动参数this.configureEnvironment(environment, applicationArguments.getSourceArgs());// 创建ConfigurationPropertySourcesConfigurationPropertySources.attach(environment);// 2.通过发送事件的方式加载Spring配置listeners.environmentPrepared(bootstrapContext, environment);// 将DefaultPropertiesPropertySource移动到数组尾部DefaultPropertiesPropertySource.moveToEnd(environment);Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");this.bindToSpringApplication(environment);if (!this.isCustomEnvironment) {EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());}// 更新ConfigurationPropertySourcesConfigurationPropertySources.attach(environment);return environment;
}

创建ConfigurableEnvironment对象源码如下:

private ConfigurableEnvironment getOrCreateEnvironment() {// 此时environment为nullif (this.environment != null) {return this.environment;} else {// 调用DefaultApplicationContextFactory创建environmentConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);}return (ConfigurableEnvironment)(environment != null ? environment : new ApplicationEnvironment());}
}// DefaultApplicationContextFactory#createEnvironment
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {// 调用静态方法引用ApplicationContextFactory::createEnvironment创建environmentreturn (ConfigurableEnvironment)this.getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, (Supplier)null);
}private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {Iterator var4 = SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, this.getClass().getClassLoader()).iterator();Object result;do {if (!var4.hasNext()) {return defaultResult != null ? defaultResult.get() : null;}ApplicationContextFactory candidate = (ApplicationContextFactory)var4.next();// 创建environment,实际调用的是Factory#createEnvironmentresult = action.apply(candidate, webApplicationType);} while(result == null);return result;
}

这里的静态方法引用调用的是AnnotationConfigServletWebServerApplicationContext.Factory#createEnvironment,创建的对象是ApplicationServletEnvironment的实例

public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {return webApplicationType != WebApplicationType.SERVLET ? null : new ApplicationServletEnvironment();
}

2.2 读取系统配置

ApplicationServletEnvironment创建实例时会调用父类AbstractEnvironment的构造函数初始化环境信息

public AbstractEnvironment() {this(new MutablePropertySources());
}protected AbstractEnvironment(MutablePropertySources propertySources) {this.logger = LogFactory.getLog(this.getClass());this.activeProfiles = new LinkedHashSet();this.defaultProfiles = new LinkedHashSet(this.getReservedDefaultProfiles());this.propertySources = propertySources;this.propertyResolver = this.createPropertyResolver(propertySources);// 会调用到StandardEnvironment#customizePropertySources加载系统配置this.customizePropertySources(propertySources);
}

调用子类的StandardEnvironment#customizePropertySources加载系统配置

protected void customizePropertySources(MutablePropertySources propertySources) {// 读取系统配置propertySources.addLast(new PropertiesPropertySource("systemProperties", this.getSystemProperties()));// 读取系统信息propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", this.getSystemEnvironment()));
}// AbstractEnvironment#getSystemProperties
public Map<String, Object> getSystemProperties() {try {// 获取系统配置return System.getProperties();} catch (AccessControlException var2) {return new ReadOnlySystemAttributesMap() {@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getProperty(attributeName);} catch (AccessControlException var3) {if (AbstractEnvironment.this.logger.isInfoEnabled()) {AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());}return null;}}};}
}// AbstractEnvironment#getSystemEnvironment
public Map<String, Object> getSystemEnvironment() {if (this.suppressGetenvAccess()) {return Collections.emptyMap();} else {try {// 读取环境信息return System.getenv();} catch (AccessControlException var2) {return new ReadOnlySystemAttributesMap() {@Nullableprotected String getSystemAttribute(String attributeName) {try {return System.getenv(attributeName);} catch (AccessControlException var3) {if (AbstractEnvironment.this.logger.isInfoEnabled()) {AbstractEnvironment.this.logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + var3.getMessage());}return null;}}};}}
}

到这一步,系统的环境信息就获取到了,例如java.home、os.version等,那么Spring配置文件application.properties等配置信息怎么获取的呢?

2.3 加载Spring配置

2.3.1 发布事件

加载Spring配置是通过调用listeners.environmentPrepared(bootstrapContext, environment)发布ApplicationEnvironmentPreparedEvent事件来完成的,具体看源码SpringApplicationRunListeners#environmentPrepared

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {listener.environmentPrepared(bootstrapContext, environment);});
}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {this.doWithListeners(stepName, listenerAction, (Consumer)null);
}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) {StartupStep step = this.applicationStartup.start(stepName);// 调用消费者发布事件this.listeners.forEach(listenerAction);if (stepAction != null) {stepAction.accept(step);}step.end();
}

doWithListeners中会调用listener.environmentPrepared(bootstrapContext, environment)发布事件,源码见EventPublishingRunListener#environmentPrepared

public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

可见发布的事件类型是ApplicationEnvironmentPreparedEvent,进入multicastEvent方法

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {// 调用监听器处理事件invokeListener(listener, event);}}
}protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}
}@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {listener.onApplicationEvent(event);}// 省略异常处理代码...
}

最终调用ApplicationListener的onApplicationEvent方法处理事件的

2.3.2 注册事件处理器

这里我们可能疑惑,这些事件处理器是怎么注册的呢?

首先SpringBoot项目启动时,会调用SpringApplication构造器设置listeners

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 设置ApplicationListenersetListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}

进入getSpringFactoriesInstances(ApplicationListener.class)方法内部

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// 从配置文件中获取listener类名Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 实例化List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

通过读取spring.factories配置文件获取listener类名,然后实例化对象

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {// 获取META-INF/spring.factories资源文件URLEnumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);// 读取配置文件中的值Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}result.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;
}

spring-boot的jar包中的META-INF/spring.factories配置如下:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

所以EnvironmentPostProcessorApplicationListener会被添加到容器中

当调用SpringApplication.run时,EventPublishingRunListener也会被创建,源码如下:

public ConfigurableApplicationContext run(String... args) {// 省略其他代码...// 创建EventPublishingRunListenerSpringApplicationRunListeners listeners = getRunListeners(args);// 省略其他代码...
}private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = getClassLoader();// 从配置文件中获取SpringApplicationRunListener的类名Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 实例化对象List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

spring-boot的jar包中的META-INF/spring.factories配置如下:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

所以EventPublishingRunListener对象会被创建,源码如下:

public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.initialMulticaster = new SimpleApplicationEventMulticaster();// 添加ApplicationListener到initialMulticaster中for (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}
}

application.getListeners()会获取到上文中添加到容器中的listeners,将每个listener添加到initialMulticaster中,源码如下:

public void addApplicationListener(ApplicationListener<?> listener) {synchronized (this.defaultRetriever) {Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {this.defaultRetriever.applicationListeners.remove(singletonTarget);}// 添加到defaultRetriever的applicationListeners成员变量中this.defaultRetriever.applicationListeners.add(listener);this.retrieverCache.clear();}
}

这样defaultRetriever的applicationListeners成员变量就保存了这些listener对象

再看看multicastEvent方法中的getApplicationListeners(event, type),会调用retrieveApplicationListeners

private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {List<ApplicationListener<?>> allListeners = new ArrayList<>();Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);Set<ApplicationListener<?>> listeners;Set<String> listenerBeans;synchronized (this.defaultRetriever) {// 从defaultRetriever中获取listenerslisteners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 过滤符合条件的listenerfor (ApplicationListener<?> listener : listeners) {if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}// 省略部分代码...return allListeners;
}

这样就获取到了listener处理事件,其中就包括EnvironmentPostProcessorApplicationListener

2.3.3 处理事件

EnvironmentPostProcessorApplicationListener#onApplicationEvent会处理上述发布的ApplicationEnvironmentPreparedEvent事件

public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationEnvironmentPreparedEvent) {// 处理ApplicationEnvironmentPreparedEvent事件this.onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent)event);}if (event instanceof ApplicationPreparedEvent) {this.onApplicationPreparedEvent();}if (event instanceof ApplicationFailedEvent) {this.onApplicationFailedEvent();}
}private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment environment = event.getEnvironment();SpringApplication application = event.getSpringApplication();// 获取环境相关的后处理器for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),event.getBootstrapContext())) {postProcessor.postProcessEnvironment(environment, application);}
}

getEnvironmentPostProcessors会读取spring.factories配置文件获取EnvironmentPostProcessor

List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resourceLoader,ConfigurableBootstrapContext bootstrapContext) {ClassLoader classLoader = (resourceLoader != null) ? resourceLoader.getClassLoader() : null;// 这个postProcessorsFactory是通过构造器添加的EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
}

this.postProcessorsFactory.apply(classLoader)会调用如下EnvironmentPostProcessorsFactory#fromSpringFactories,加载EnvironmentPostProcessor

static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {return new ReflectionEnvironmentPostProcessorsFactory(classLoader,SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}

spring-boot的jar包中的META-INF/spring.factories配置如下:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

getEnvironmentPostProcessors就会实例化EnvironmentPostProcessor,这样ConfigDataEnvironmentPostProcessor对象就会被创建,调用它的postProcessEnvironment时获取配置信息

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();// 创建ConfigDataEnvironment获取配置信息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是SpringBoot配置数据加载和管理的核心组件,它的构造函数如下,主要创建了ConfigDataLocationResolvers、ConfigDataLoaders及ConfigDataEnvironmentContributors

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());// 创建ConfigDataEnvironmentContributorsthis.contributors = createContributors(binder);
}// ConfigDataLocationResolvers构造器
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,Binder binder, ResourceLoader resourceLoader) {this(logFactory, bootstrapContext, binder, resourceLoader, SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, resourceLoader.getClassLoader()));
}// ConfigDataLoaders构造器
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,ClassLoader classLoader) {this(logFactory, bootstrapContext, classLoader,SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, classLoader));
}

这里也通过SpringFactoriesLoader#loadFactoryNames从配置文件中创建ConfigDataLocationResolver及ConfigDataLoader的,spring-boot的jar包中的META-INF/spring.factories配置如下:

# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.StandardConfigDataLoader

其中StandardConfigDataLocationResolver用于解析标准的配置文件位置,例如类路径下的config目录中的applicaiton.properties或者applicaiton.yml,而StandardConfigDataLoader则用于加载标准的配置数据

创建ConfigDataEnvironmentContributors源码如下:

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;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));}}// 关键方法:getInitialImportContributorscontributors.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);
}protected ConfigDataEnvironmentContributors createContributors(List<ConfigDataEnvironmentContributor> contributors) {return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}

ConfigDataEnvironmentContributors主要作用是维护一组ConfigDataEnvironmentContributor,而每个ConfigDataEnvironmentContributor用于配置数据的加载、处理和管理

这里重点看下getInitialImportContributors(binder)获取初始导入的ConfigDataEnvironmentContributor,这决定了配置文件的加载位置及顺序

private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();// 1.指定spring.config.import要导入的额外配置文件位置addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));// 2.指定spring.config.additional-location额外的配置文件位置addInitialImportContributors(initialContributors,bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));// 3.指定spring.config.location默认配置文件位置addInitialImportContributors(initialContributors,bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));return initialContributors;
}private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,ConfigDataLocation[] locations) {// 从后往前遍历for (int i = locations.length - 1; i >= 0; i--) {initialContributors.add(createInitialImportContributor(locations[i]));}
}

在第3步中指定的配置文件地址DEFAULT_SEARCH_LOCATIONS包含:

static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
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]);
}

而addInitialImportContributors是从后往前遍历的,因此加载顺序为:

file:./
file:./config/
file:./config/*/
classpath:/
classpath:/config/

创建ConfigDataEnvironmentContributors完成后就会调用ConfigDataEnvironment#processAndApply加载和解析配置文件,源码如下:

void processAndApply() {// 创建数据导入器ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,this.loaders);registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);// 执行加载以及解析初始的配置文件ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);// 处理profiles相关的配置,包括云平台配置及本地环境配置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");// 调用ConfigDataEnvironmentContributors加载配置contributors = contributors.withProcessedImports(importer, null);registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);return contributors;
}private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,ConfigDataImporter importer, ConfigDataActivationContext activationContext) {this.logger.trace("Processing config data environment contributors with initial activation context");contributors = contributors.withProcessedImports(importer, activationContext);registerBootstrapBinder(contributors, activationContext, DENY_INACTIVE_BINDING);return contributors;
}private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,ConfigDataImporter importer, ConfigDataActivationContext activationContext) {this.logger.trace("Processing config data environment contributors with profile activation context");contributors = contributors.withProcessedImports(importer, activationContext);registerBootstrapBinder(contributors, activationContext, ALLOW_INACTIVE_BINDING);return contributors;
}

processInitial、processWithoutProfiles及processWithProfiles的逻辑都类似,会调用ConfigDataEnvironmentContributors#withProcessedImports方法

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;// 遍历所有的ConfigDataEnvironmentContributorwhile (true) {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) {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));// 执行解析和加载数据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++;}
}

调用ConfigDataImporter#resolveAndLoad

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);}
}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 {// 加载配置文件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);
}

读取文件是从最后一个开始读取,所以配置优先级顺序从高到低为:

file:./config/*/
file:./config/
file:./
classpath:/config/
classpath:/

这里通过StandardConfigDataLoader读取配置文件,StandardConfigDataLoader#load源码如下:

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());// 调用PropertySourceLoader接口加载配置文件List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;return new ConfigData(propertySources, options);
}

PropertySourceLoader接口的实现类有PropertiesPropertySourceLoader和YamlPropertySourceLoader,其中PropertiesPropertySourceLoader可以加载properties和xml格式的配置文件,YamlPropertySourceLoader可以加载yml及yaml格式的配置文件

相关文章:

Spring原理分析--获取Environment资源对象

1.使用getEnvironment()获取环境信息 ApplicationContext接口继承了EnvironmentCapable接口&#xff0c;可以通过getEnvironment()获取Environment配置信息&#xff0c;例如&#xff1a; SpringBootApplication public class A01 {public static void main(String[] args) th…...

Android GPU渲染SurfaceFlinger合成RenderThread的dequeueBuffer/queueBuffer与fence机制(2)

Android GPU渲染SurfaceFlinger合成RenderThread的dequeueBuffer/queueBuffer与fence机制&#xff08;2&#xff09; 计算fps帧率 用 adb shell dumpsys SurfaceFlinger --list 查询当前的SurfaceView&#xff0c;然后有好多行&#xff0c;再把要查询的行内容完整的传给 ad…...

人民币数字和中文汉字转换

在PHP中&#xff0c;将人民币的中文汉字金额转换为数字&#xff0c;或者将数字转换为人民币的中文汉字金额&#xff0c;通常需要自定义一些函数来实现这一转换过程。下面分别给出这两个转换的示例代码。 数字转人民币中文汉字 function numberToChinese($num) { $cnNums arr…...

07_Flutter使用NestedScrollView+TabBarView滚动位置共享问题修复

07_Flutter使用NestedScrollViewTabBarView滚动位置共享问题修复 一.案发现场 可以看到&#xff0c;上图中三个列表的滑动位置共享了&#xff0c;滑动其中一个列表&#xff0c;会影响到另外两个&#xff0c;这显然不符合要求&#xff0c;先来看下布局&#xff0c;再说明产生这个…...

Java解决垂直鉴权问题(对垂直权限进行校验)

Java解决垂直鉴权问题&#xff08;对垂直权限进行校验&#xff09; 文章目录 Java解决垂直鉴权问题&#xff08;对垂直权限进行校验&#xff09;前言一、垂直鉴权是什么&#xff1f;二、实现过程1.新建接口权限菜单映射表2.项目初始化时加载接口菜单映射关系3.自定义过滤器拦截…...

【MySQL工具】pt-heartbeat

功能 pt-heartbeat - 监控 MySQL 复制延迟。 用法 pt-heartbeat [OPTIONS] [DSN] --update|--monitor|--check|--stop pt-heartbeat 用于测量 MySQL 或 PostgreSQL 服务器上的复制延迟。您可以使用它来更新主服务器或监控从服务器。如果可能&#xff0c;MySQL 连接选项将从您…...

实现vant的年月日时分秒组件

方法&#xff1a;van-datetime-picker&#xff08;type&#xff1a;datetime&#xff09;和 van-picker结合实现。 <template><div class"datetimesec-picker"><van-datetime-pickerref"timePickerRef"type"datetime" //年月日时…...

typescript 命名空间、装饰器

1、命名空间 命名空间&#xff1a;在代码量较大的情况下&#xff0c;为了避免各种变量命名的冲突&#xff0c;可将相似功能的函数、类、接口等放置到命名空间内。同Java的包.Net的命名空间一样&#xff0c;typescript 的命名空间可以将代码包裹起来&#xff0c;只对外暴露需要在…...

GPT问答SAP BW

以下回答由GPT-3.5回答,仅供参考. 这个AI工具超好用&#xff0c;每天都有免费额度&#xff0c;写文章、总结长视频、画图等&#xff0c;都几秒搞定&#xff01;快去下载Sider Chrome或Edge插件&#xff0c;薅羊毛&#xff01; https://sider.ai/invited?c43b289bf2616575daecf…...

使用zdppy_amauth开发激活用户接口

服务端代码&#xff1a; 1、创建数据库连接对象2、初始化数据库3、声明一个上下文4、挂载用户相关的路由&#xff0c;这里主要由 用户登录接口用户注册注册获取用户列表接口激活指定用户接口 5、启动服务 import mcrud import api import amauth import env import contextli…...

c++ memset 指针示例

目录 C 传一个float指针&#xff0c;在函数内部修改指针的值 c memset 指针示例 C 传一个float指针&#xff0c;在函数内部修改指针的值 #include <iostream>// 定义一个函数&#xff0c;它接受一个指向float的指针 void modifyValue(float* ptr) {// 通过解引用指针来…...

24考研双非上岸武汉理工大学电子信息专硕,855考研经验

目录 一、考研择校经验 二、武理考研初试经验 三、武理考研复试经验 一、考研择校经验 我建议学弟学妹们确定院校时没必要一上来就说我一定要考某个院校。其实考哪个学校是要在考研备考的过程中慢慢探索&#xff0c;慢慢研究的&#xff0c;不过最晚9月初一定要确定院校了&a…...

使用KubeKey 快速交付k8s v1.28.8集群

文章目录 服务器配置使用kubekey部署k8s1. 操作系统基础配置2. 安装部署 K8s2.1 下载 KubeKey2.2 创建 K8s 集群部署配置文件 3. 验证 K8s 集群3.1 验证集群状态 4. 部署测试资源5.验证服务 服务器配置 主机名IPCPU内存系统盘数据盘用途vm-16-11-ubuntu192.168.9.131128256Gi5…...

nginx--压缩https证书favicon.iconginx隐藏版本号 去掉nginxopenSSL

压缩功能 简介 Nginx⽀持对指定类型的⽂件进行压缩然后再传输给客户端&#xff0c;而且压缩还可以设置压缩比例&#xff0c;压缩后的文件大小将比源文件显著变小&#xff0c;这样有助于降低出口带宽的利用率&#xff0c;降低企业的IT支出&#xff0c;不过会占用相应的CPU资源…...

通俗的理解网关的概念的用途(四):什么是网关设备?(网络层面)

任何一台Windows XP操作系统之后的个人电脑、Linux操作系统电脑都可以简单的设置&#xff0c;就可以成为一台具备“网关”性质的设备&#xff0c;因为它们都直接内置了其中的实现程序。MacOS有没有就不知道&#xff0c;因为没用过。 简单的理解&#xff0c;就是运行了具备第二…...

Spring JdbcTemplate实现自定义动态sql拼接功能

需求描述&#xff1a; sql 需要能满足支持动态拼接&#xff0c;包含 查询字段、查询表、关联表、查询条件、关联表的查询条件、排序、分组、去重等 实现步骤&#xff1a; 1&#xff0c;创建表及导入测试数据 CREATE TABLE YES_DEV.T11 (ID BINARY_BIGINT NOT NULL,NAME VARCH…...

第十一篇:操作系统新纪元:智能融合、量子跃迁与虚拟现实的交响曲

操作系统新纪元&#xff1a;智能融合、量子跃迁与虚拟现实的交响曲 1 引言 在数字化的浪潮中&#xff0c;操作系统如同一位智慧的舵手&#xff0c;引领着信息技术的航船穿越波涛汹涌的海洋。随着人工智能、物联网、量子计算等前沿技术的蓬勃发展&#xff0c;操作系统正站在一个…...

【大数据】学习笔记

文章目录 [toc]NAT配置IP配置SecureCRT配置PropertiesTerminal Java安装环境变量配置 Hadoop安装修改配置文件hadoop-env.shyarn-env.shslavescore-site.xmlhdfs-site.xmlmapred-site.xmlyarn-site.xml 环境变量配置 IP与主机名映射关系配置hostname配置映射关系配置 关闭防火墙…...

PHP 框架安全:ThinkPHP 序列 漏洞测试.

什么是 ThinkPHP 框架. ThinkPHP 是一个流行的国内 PHP 框架&#xff0c;它提供了一套完整的安全措施来帮助开发者构建安全可靠的 web 应用程序。ThinkPHP 本身不断更新和改进&#xff0c;以应对新的安全威胁和漏洞。 目录&#xff1a; 什么是 ThinkPHP 框架. ThinkPHP 框架…...

厂家自定义 Android Ant编译流程源码分析

0、Ant安装 Windows下安装Ant&#xff1a; ant 官网可下载 http://ant.apache.org ant 环境配置&#xff1a; 解压ant的包到本地目录。 在环境变量中设置ANT_HOME&#xff0c;值为你的安装目录。 把ANT_HOME/bin加到你系统环境的path。 Ubuntu下安装Ant&#xff1a; sudo apt…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...