目录java
最近在学习Spring Boot相关的课程,过程当中以笔记的形式记录下来,方便之后回忆,同时也在这里和你们探讨探讨,文章中有漏的或者有补充的、错误的都但愿你们可以及时提出来,本人在此先谢谢了!web
开始以前呢,但愿你们带着几个问题去学习:
一、Spring Boot 外部化配置是什么?
二、总体流程或结构是怎样的?
三、核心部分是什么?
四、怎么实现的?
这是对自个人提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。好了,接下来进入主题。redis
这篇文章咱们就来讨论 Spring Boot
的外部化配置功能,该功能主要是经过外部的配置资源实现与代码的相互配合,来避免硬编码,提供应用数据或行为变化的灵活性。相信小伙伴们在平常工做中都有使用过,如在 properties
或者 YAML
文件中定义好key value格式的数据后,就可在程序中经过 @Value
注解获取该value值。还能够定义一些同外部组件约定好的key,如以 spring
或 redis
等组件名为前缀的key,以后相应的组件就可读取到该value值进行工做。固然,这只是外部化配置的一小部份内容,接下来进行详细讨论。spring
先来看看外部化配置的几种资源类型,除了 properties
和 YAML
外,还有环境变量、系统属性、启动参数等。全部的资源类型将近二十种,这里只介绍咱们比较熟悉的:
一、properties :这个应该都知道,就是在以 .properties
为后缀的文件中定义key value格式数据。
二、YAML:文件格式是以 .yml
为后缀,文件中的数据也是key value格式,以下:架构
user: name: loong age: 10
这里的key就是 user.name
、 user.age
。app
三、 环境变量:这是经过 System.getenv()
方式获取的默认配置,也是key value格式,下面列出部分配置,其它的还请自行了解,以下:dom
名称 | Key |
---|---|
Java安装目录 | JAVA_HOME |
classpath环境变量 | CLASSPATH |
用户临时文件目录 | TEMP |
计算机名 | COMPUTERNAME |
用户名 | USERNAME |
四、 系统属性:这是经过 System.getProperties()
方式获取的默认配置,也是key value格式,下面列出部分配置,其它的还请自行了解,以下:ide
名称 | Key |
---|---|
运行时环境版本 | java.version Java |
Java安装目录 | java.home |
要使用的 JIT编译器的名称 | java.compiler |
操做系统的架构 | os.arch |
操做系统的版本 | os.version |
五、 启动参数:这个在 Spring Boot SpringApplication
启动类(二)这篇文章中讨论过。一种是在 jar
包运行时行时传递的参数,如:java -jar xxx.jar name=张三 pwa=123
,还有一种是在 IDEA 的 Program arguments
中输入数据:函数
能够看到,外部化配置中的数据都是key value格式。这里还要注意它们的加载顺序,当key相同时,会出现覆盖的状况。post
接下来,咱们的重心来围绕 properties
和 YAML
配置文件,这二者也是咱们平常工做中经常使用的。首先来看取值方式,在 Spring
时代有 Environment
、 @Value
、 XML
三种方式,在 Spring Boot
时代则是 @ConfigurationProperties
方式。其中,涉及到了一个核心类,它就是 Environment
,该对象不只能够获取全部的外部化配置数据,就连另外几种取值方式的数据来源也是从该类中获取。这里,主要对 Environment
和 @ConfigurationProperties
进行详细讨论,笔者认为 Environment
和 @ConfigurationProperties
才是 Spring Boot
外部化配置的核心所在。
该类在 Spring Boot SpringApplication
启动类(二) 的 2.3 章节有简要说明过,这里咱们展开详细讨论。首先回顾一下代码:
public class SpringApplication { ... public ConfigurableApplicationContext run(String... args) { ... try { ... ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); ... } ... }
在 SpringApplication
运行阶段的 run
方法中经过 prepareEnvironment
方法了建立 ConfigurableEnvironment
的实现类对象,ConfigurableEnvironment
是一个接口,且继承了 Environment
。进入建立方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
首先看第一行,经过 getOrCreateEnvironment
方法建立 ConfigurableEnvironment
对象:
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
在 Spring Boot SpringApplication
启动类(二) 的 2.3 章节说过, webApplicationType
存储的是应用的类型,有 Reactive
、Servlet
等,是在 SpringApplication
准备阶段推导出来的,而本项目推导出来是 Servlet
类型,因此实例化的是 StandardServletEnvironment
对象:
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment { public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams"; public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams"; public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); } ... }
而该类又继承了 StandardEnvironment
类。且重写了 customizePropertySources
方法,并调用了父类的 customizePropertySources
方法。咱们继续往下深刻:
public class StandardEnvironment extends AbstractEnvironment { public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast( new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast( new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); } }
继续看它的 AbstractEnvironment
父抽象类:
public abstract class AbstractEnvironment implements ConfigurableEnvironment { ... private final MutablePropertySources propertySources = new MutablePropertySources(); ... public AbstractEnvironment() { customizePropertySources(this.propertySources); } ... }
能够看到,最终会有一个 AbstractEnvironment
抽象类。在 StandardServletEnvironment
初始化时,会调用 AbstractEnvironment
的构造方法,里面调用了子类重写的 customizePropertySources
方法,且入参是 MutablePropertySources
对象,该对象是 Environment
的一个属性,是底层真正存储外部化配置的。以后, StandardServletEnvironment
和 StandardEnvironment
的 customizePropertySources
方法相继执行,主要是往 MutablePropertySources
对象中添加外部化配置。其中咱们前面所说的环境变量和系统属性是在 StandardEnvironment
重写的方法中进行加载。
咱们回到外面的 prepareEnvironment
方法,继续往下走。接着执行的是 configureEnvironment
方法,该方法主要是把启动参数加入到 MutablePropertySources
中。以后,咱们断点看看有多少种外部化配置:
有五种,且真正存储数据的是 MutablePropertySources
中的 PropertySource
实现类集合。
这里简要介绍一下 PropertySource
,咱们将其称之为配置源,官方定义它是外部化配置的API描述方式,是外部化配置的一个媒介。 用咱们的话来讲,它是一个抽象类,提供了统一存储外部化配置数据的功能,而每种外部化配置有具体的实现类,主要提供不一样的基础操做,如 get
、contains
等 。咱们来看看 PropertySource
对象的数据格式,通常包含:
name : 外部化配置的名称
source : 存储配置中的数据,底层通常数据格式都是key value
咱们继续往下走,接着调用了 SpringApplicationRunListeners
的 environmentPrepared
方法。在上篇文章
Spring Boot SpringApplication
启动类(二) 的 2.1 小节讲过,当 Spring Boot
执行到某一阶段时,会经过 Spring
的 SimpleApplicationEventMulticaster
事件广播器进行事件广播,以后 ,相应监听器就会监听到该事件,执行调监听器的 onApplicationEvent
方法。这里表示 Spring Boot
到了 ConfigurableEnvironment
构建完成时阶段。咱们进入该方法:
class SpringApplicationRunListeners { ... private final List<SpringApplicationRunListener> listeners; public void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } ... }
真正调用的是 SpringApplicationRunListener
集合中的 environmentPrepared
方法。 SpringApplicationRunListener
是一个接口,它具备惟一实现类 EventPublishingRunListener
:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { ... private final SimpleApplicationEventMulticaster initialMulticaster; @Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); } ... }
能够看到,最终经过 SimpleApplicationEventMulticaster
的 multicastEvent
方法发布 ApplicationEnvironmentPreparedEvent
事件。上面说过, Spring Boot
监听器会监听到该事件,其中一个名为 ConfigFileApplicationListener
的监听器,监听到该事件后会进行加载 application
和 YAML
配置文件的操做,接下来,咱们具体的来看一看该类实现。
咱们直接进入该类:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { ... private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; private static final String DEFAULT_NAMES = "application"; @Override public void onApplicationEvent(ApplicationEvent event) { // 一、经过 instanceof 判断事件的类型,若是是 ApplicationEnvironmentPreparedEvent 事件,则执行 onApplicationEnvironmentPreparedEvent 方法 if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } ... } private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) { // 二、调用 loadPostProcessors 方法,返回 Environment 的后置处理器集合,咱们跳到 2.1 查看方法实现 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); // 2.二、把本身也加入该集合 postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); // 2.三、遍历 EnvironmentPostProcessor 集合,执行它们的 postProcessEnvironment 方法,咱们跳到 3 查看当前类的该方法实现 for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } // 2.1 是咱们比较熟悉的 loadFactories 方法,在 Spring Boot 自动装配(二) 的 2.1.2 小节讲过,loadFactories 方法是从 spring.factories 文件中加载 key 为 EnvironmentPostProcessor 的实现类集合 List<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); } // 三、 执行到该方法时,会调用 addPropertySources 方法,入参是上文加载 ConfigurableEnvironment 对象 @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); } protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); // 四、 咱们主要关注这里,经过 Loader 的构造方法建立该对象,并调用它的 load 方法 new Loader(environment, resourceLoader).load(); } private class Loader { private final ConfigurableEnvironment environment; private final List<PropertySourceLoader> propertySourceLoaders; // 4.一、 构造方法中会初始化一些属性 Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { ... this.environment = environment; // 又是咱们比较熟悉的 loadFactories 方法,在 Spring Boot 自动装配(二) 的 2.1.2 小节讲过,loadFactories 方法是从 spring.factories 文件中加载 key 为 PropertySourceLoader 的实现类集合。这里加载的是 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 两个实现类,看类名可初步判定是处理 properties 和 YAML 文件的 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader()); } public void load() { ... // 五、这里会继续调用它重载的 load 方法 load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); ... // 九、这是最后一步,将当前类中的 MutablePropertySources 中的 PropertySource 对象,所有塞到 ConfigurableEnvironment 的 MutablePropertySources 对象中。咱们跳到 9.1 进行查看 addLoadedPropertySources(); } private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 5.一、首先执行 getSearchLocations 方法,看方法名大体能猜出是获取搜索路径的,咱们跳到 5.1.1 查看该方法的实现 getSearchLocations().forEach((location) -> { // 5.二、开始遍历该集合,先判断该路径是不是以反斜杠结尾,是的话则该路径为文件夹;不是的话,则该路径为文件的完整路径,相似于 classPath:/application.properties boolean isFolder = location.endsWith("/"); // 5.三、 若是是文件夹路径,则经过 getSearchNames 获取文件的名称,不是则返回空集合,咱们跳到 5.3.1 查看 getSearchNames 方法 Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; // 5.四、再调用 load 的重载方法,这里,location 是路径名,name是文件名,咱们跳到 6 进行查看 names.forEach((name) -> load(location, name, profile, filterFactory, consumer)); }); } // 5.1.一、这个方法就是获取加载 application 和 YAML 文件路径的 private Set<String> getSearchLocations() { // 能够看到 CONFIG_LOCATION_PROPERTY 的值为 spring.config.location,也就是说,先判断咱们有没有手动设置搜索路径,有的话直接返回该路径。该值通常经过启动参数的方式设置 if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); } // 该 CONFIG_ADDITIONAL_LOCATION_PROPERTY 变量的值为 spring.config.additional-location,这也是用于手动设置搜索路径,不过和上面不一样的是,不会覆盖 接下来默认的搜索路径 Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); // 这里就是获取默认的搜索路径,经过 DEFAULT_SEARCH_LOCATIONS 变量的值 classpath:/,classpath:/config/,file:./,file:./config/,将该值用逗号分隔,加入集合并返回。到这一步,咱们至少获取到了4个加载 application 和 YAML 文件的路径 locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; } // 5.3.1 private Set<String> getSearchNames() { // CONFIG_LOCATION_PROPERTY 变量值为 spring.config.name ,一样先判断有没有手动设置文件名称,有的话,直接返回 if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { String property = this.environment.getProperty(CONFIG_NAME_PROPERTY); return asResolvedSet(property, null); } // 若是没有,则经过 DEFAULT_NAMES 变量值返回默认的文件名,变量值为 application return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); } // 六、 private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { // 6.一、 上面 5.2 说过 name 为空时,表示 location 是完整的文件路径。以后进入这个 if if (!StringUtils.hasText(name)) { // 6.1.一、propertySourceLoaders 属性是在 4.1 处被初始化的,存储的是 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 两个类。这里对这两个类进行遍历 for (PropertySourceLoader loader : this.propertySourceLoaders) { // 咱们跳到 6.1.2 查看 canLoadFileExtension 方法实现,入参 location 是文件的完整路径 if (canLoadFileExtension(loader, location)) { // 这里又是一个 load 重载方法,咱们跳到 7 进行查看 load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer); return; } } } Set<String> processed = new HashSet<>(); for (PropertySourceLoader loader : this.propertySourceLoaders) { // 6.2 这里和 6.1.3 相似,获取文件扩展名 for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { // 进入 6.三、查看该方法实现。关注重点的两个参数:一个是路径名 + 文件名,还有一个 “.” +文件扩展名 loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer); } } } } // 6.1.二、 该方法做用是 判断 name 完整路径名是否以指定的文件扩展名结尾 private boolean canLoadFileExtension(PropertySourceLoader loader, String name) { // 6.1.三、调用 PropertySourceLoader 的 getFileExtensions 方法。当你的实现类是 PropertiesPropertySourceLoader 时,该方法返回 properties、xml;若是是 YamlPropertySourceLoader 则返回 yml、yaml。从这里能够看出,能被处理的文件格式有这四种 return Arrays.stream(loader.getFileExtensions()) .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name, fileExtension)); } // 6.3 到了这里,prefix 和 fileExtension 都是进行拼接好的值,如 prefix = classpath:/applicarion,fileExtension = .properties private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { ... // 这里一样调用节点 7 的重载方法,经过 prefix + fileExtension 造成完整的文件路径名,经过入参进行传递。如 classpath:/applicarion.properties load(loader, prefix + fileExtension, profile, profileFilter, consumer); } // 七、 private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, DocumentConsumer consumer) { try { // 这里调用 ResourceLoader 的 getResource 方法,经过 location 文件路径,读取获取该文件资源,以后就好办了 Resource resource = this.resourceLoader.getResource(location); ... // 具体解析在过程 loadDocuments 中,这里就不继续跟踪了,大体是以流的方式解析文件。解析以后会生成一个 PropertySource 对象,该对象在上面说过,表示一个外部化配置源对象,存储配置中的数据。以后,会将该对象封装到 Document 中 List<Document> documents = loadDocuments(loader, name, resource); ... if (!loaded.isEmpty()) { // 遍历 documents 集合,当执行 consumer.accept 时会进入 addToLoaded 方法,这是 Java8 的写法。consumer 对象参数来自节点 5 。咱们跳到 8 查看 addToLoaded 实现 loaded.forEach((document) -> consumer.accept(profile, document)); if (this.logger.isDebugEnabled()) { StringBuilder description = getDescription("Loaded config file ", location, resource, profile); this.logger.debug(description); } } } catch (Exception ex) { throw new IllegalStateException("Failed to load property " + "source from location '" + location + "'", ex); } } // 八、BiConsumer 是 JAVA8 的函数接口,表示定义一个带有两个参数且不返回结果的操做,经过节点 5 咱们知道,这个操做是 MutablePropertySources::addFirst 。 private DocumentConsumer addToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, boolean checkForExisting) { return (profile, document) -> { if (checkForExisting) { for (MutablePropertySources merged : this.loaded.values()) { if (merged.contains(document.getPropertySource().getName())) { return; } } } MutablePropertySources merged = this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources()); // 当调用 BiConsumer 的 accept 方法时,定义的操做会执行,两个入参分别是 MutablePropertySources 对象和配置文件源对象 PropertySource。该操做会调用 MutablePropertySources 的 addFirst 方法把该配置文件源对象添加至其中。最后咱们去看看前面 load 方法中的最后一步 9 addMethod.accept(merged, document.getPropertySource()); }; } // 9.1 private void addLoadedPropertySources() { // 获取当前上下文环境中的 MutablePropertySources 对象 MutablePropertySources destination = this.environment.getPropertySources(); // 获取当前类中的 MutablePropertySources 集合 List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values()); Collections.reverse(loaded); String lastAdded = null; Set<String> added = new HashSet<>(); // 遍历 loaded 集合及其中全部的 PropertySource ,也就是 application 或 YAML 配置文件源对象 for (MutablePropertySources sources : loaded) { for (PropertySource<?> source : sources) { if (added.add(source.getName())) { // 咱们进入 9.2 查看该方法,主要参数是上下文环境中的 MutablePropertySources 对象和配置文件源对象 addLoadedPropertySource(destination, lastAdded, source); lastAdded = source.getName(); } } } } // 9.2 private void addLoadedPropertySource(Mutab lePropertySources destination, String lastAdded, PropertySource<?> source) { if (lastAdded == null) { if (destination.contains(DEFAULT_PROPERTIES)) { destination.addBefore(DEFAULT_PROPERTIES, source); } else { destination.addLast(source); } } else { // 最后经过将 source 添加到 environment 中的 MutablePropertySources 对象中。 destination.addAfter(lastAdded, source); } } // 至此,properties 和 YAML 配置文件就被加载到了上下文环境共享的 Environment 中,以后如 @Value 等获取值都是从该对象中获取 } }
能够看到,ConfigFileApplicationListener
主要功能就是将 properties
和 YAML
文件加载到 Environment
中。另外还存在一个 @PropertySource
注解,也是加载指定的配置文件到 Environment
中。
咱们回到最外面的 prepareEnvironment
方法,来看看执行完监听方法时 ConfigurableEnvironment
中加载了多少种外部化配置:
有七种,包括新增的 properties
配置文件。
以后还有一个操做,经过 ConfigurationPropertySources.attach
关联 SpringConfigurationPropertySources
,这个是下一小节须要用到。咱们进入 attach 方法查看:
public final class ConfigurationPropertySources { private static final String ATTACHED_PROPERTY_SOURCE_NAME = "configurationProperties"; public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); // 获取 ConfigurableEnvironment 中的 MutablePropertySources MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); // 获取名为 configurationProperties 的外部化配置源对象 PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); // 若是存在,则把该对象移除 if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } // 不存在,则添加一个配置源对象,具体对象类型为 ConfigurationPropertySourcesPropertySource,源对象中的数据为 SpringConfigurationPropertySources if (attached == null) { sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } } }
到这里,ConfigurableEnvironment
又新增了一个 ConfigurationPropertySourcesPropertySource
类型的配置源对象。咱们主要来关注 SpringConfigurationPropertySources
对象,能够看到,这里是经过它的带参构造器建立该对象,参数 sources
是从 ConfigurableEnvironment
中获取的 MutablePropertySources
对象。咱们进入 SpringConfigurationPropertySources
类中查看:
class SpringConfigurationPropertySources implements Iterable<ConfigurationPropertySource> { ... private final Iterable<PropertySource<?>> sources; SpringConfigurationPropertySources(Iterable<PropertySource<?>> sources) { Assert.notNull(sources, "Sources must not be null"); this.sources = sources; } ... }
能够看到,外部 ConfigurableEnvironment
的 MutablePropertySources
关联到了该类中的 Iterable
(继承关系) 对象,这里是一个伏笔,下一节底层实现需依赖该属性。
至此, Environment
的建立过程及加载外部化配置的过程就到这里结束,咱们简要回顾一下该流程:
Environment
是一个较为特殊的类,术语称之为应用运行时的环境。它存储了全部的外部化配置,能够经过它获取任意配置数据,而且 @Value
、 @ConfigurationProperties
等其它获取配置数据的方式都依赖于该类。Environment
,有 Servlet
、Reactive
、非 Web 类型。PropertySource
配置源对象。properties
和 YAML
的方式。主要是经过回调 Spring Boot
的监听器 ConfigFileApplicationListener
进行处理。因篇幅过长, @ConfigurationProperties 内容另起一章。