ConfigFileApplicationListener
收到 ApplicationEnvironmentPreparedEvent
事件后通过 SPI 加载所有的 EnvironmentPostProcessor
实现,触发其 postProcessEnviroment
方法。
SpringApplication.run() -> SpringFactoriesLoader.loadFactories(ApplicationListener) -> SpringApplication.prepareEnviroment() -> EventPublishingRunListener.enviromentPrepared(ApplicationEnviromentPraparedEvent) -> SimpleApplicationEventMulticaster.multicastEvent() -> ConfigFileApplicationListener.onApplicationOnEnviromentPreparedEvent() -> EnviromentPostProcessor.postProcessEnviroment()
比较重要的 EnviromentPostProcessor
实现是 HostInfoEnvironmentPostProcessor
和 ConfigFileApplicationListener
。
获取本机的 主机名和IP地址,封装在 PropertySource
添加到 environment 里。
ConfigFileApplicationListener
自身也实现了 EnvironmentPostProcessor
,通过内部类 Loader 去加载配置文件,其主要流程如下:
classpath:/,classpath:/config/,file:./,file:./config/
。 /
结尾的则认为是一个文件,直接加载,否则,找出所有的搜索文件名 name 进行迭代搜索,默认的搜索文件名是 “application”。 PropertySourcesLoader
找出支持的所有配置文件后缀进行迭代。 location + name + "-" + profile + "." + ext
组成的一个具体的完整路径,通过 PropertiesLoader.load
方法加载该路径指向的配置文件。 PropertiesLoader.load
内部又根据配置文件的后缀用不同的 PropertySourceLoader
去加载得到一个 PropertySource
。 PropertySource
,找出里面激活的 profile,添加到 proflie 集合里进行迭代。 PropertySourceLoader
是用来加载 PropertySource
的一个策略接口,有两个具体的实现类 PropertiesPropertySourceLoader
和 YamlPropertySourceLoader
,前者用于加载 properties/xml
后缀的配置文件,后者用于加载 yml
后者的配置文件。
PropertySourcesLoader
是一个 facade 类,通 SpringFactoriesLoader
加载 PropertySourceLoader
的所有实现类。在它的 load 方法里会迭代这些实现类以加载特定后缀的配置文件。
public PropertySourcesLoader(MutablePropertySources propertySources) { Assert.notNull(propertySources, "PropertySources must not be null"); this.propertySources = propertySources; this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } public PropertySource<?> load(Resource resource, String group, String name, String profile) throws IOException { if (isFile(resource)) { String sourceName = generatePropertySourceName(name, profile); for (PropertySourceLoader loader : this.loaders) { if (canLoadFileExtension(loader, resource)) { PropertySource<?> specific = loader.load(sourceName, resource, profile); addPropertySource(group, specific); return specific; } } } return null; } private void addPropertySource(String basename, PropertySource<?> source) { if (source == null) { return; } if (basename == null) { this.propertySources.addLast(source); return; } EnumerableCompositePropertySource group = getGeneric(basename); group.add(source); logger.trace("Adding PropertySource: " + source + " in group: " + basename); if (this.propertySources.contains(group.getName())) { // 替换原有的 this.propertySources.replace(group.getName(), group); } else { // 把最新的添加到列表的首部 // 对于 PropertiesPropertySourceLoader, properties 后缀的比 xml 的先加载,优先级反而低了 this.propertySources.addFirst(group); } }
// 加载属性源到 enviroment protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { // 把随机值的属性源添加到 enviroment RandomValuePropertySource.addToEnvironment(environment); // 从配置文件加载属性源到 environment new Loader(environment, resourceLoader).load(); } // Loader 类 public void load() { this.propertiesLoader = new PropertySourcesLoader(); this.activatedProfiles = false; this.profiles = Collections.asLifoQueue(new LinkedList<Profile>()); this.processedProfiles = new LinkedList<Profile>(); // Pre-existing active profiles set via Environment.setActiveProfiles() // are additional profiles and config files are allowed to add more if // they want to, so don't call addActiveProfiles() here. // 添加已存在、激活的 profiles Set<Profile> initialActiveProfiles = initializeActiveProfiles(); this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); if (this.profiles.isEmpty()) { for (String defaultProfileName : this.environment.getDefaultProfiles()) { Profile defaultProfile = new Profile(defaultProfileName, true); if (!this.profiles.contains(defaultProfile)) { this.profiles.add(defaultProfile); } } } // 迭代过程中默认的 proflie 用 null 表示。添加到最后可以第一个出队列。 // 后面迭代的激活的 profiles 会覆写默认的配置 this.profiles.add(null); // 迭代 proflie while (!this.profiles.isEmpty()) { Profile profile = this.profiles.poll(); // 迭代要搜索的路径 for (String location : getSearchLocations()) { if (!location.endsWith("/")) { // location is a filename already, so don't search for more filenames load(location, null, profile); } else { // 迭代要搜索的配置文件名 for (String name : getSearchNames()) { load(location, name, profile); } } } this.processedProfiles.add(profile); } // 把加载到的 PropertySources 添加到 enviroment addConfigurationProperties(this.propertiesLoader.getPropertySources()); } private void load(String location, String name, Profile profile) { String group = "profile=" + ((profile != null) ? profile : ""); if (!StringUtils.hasText(name)) { // Try to load directly from the location loadIntoGroup(group, location, profile); } else { // 迭代所有支持的文件后缀 for (String ext : this.propertiesLoader.getAllFileExtensions()) { if (profile != null) { // 尝试 profile 特定的文件,文件名包含 proflie 值的 loadIntoGroup(group, location + name + "-" + profile + "." + ext, null); for (Profile processedProfile : this.processedProfiles) { if (processedProfile != null) { loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile); } } // Sometimes people put "spring.profiles: dev" in // application-dev.yml (gh-340). Arguably we should try and error // out on that, but we can be kind and load it anyway. loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile); } // Also try the profile-specific section (if any) of the normal file loadIntoGroup(group, location + name + "." + ext, profile); } } } // 如果解析的配置文件里用 spring.config.location 指定了新的位置, // 那么下一轮查找也把 spring.config.location 属性指定的位置加入搜索范围 // 默认的搜索位置有: classpath:/,classpath:/config/,file:./,file:./config/ private Set<String> getSearchLocations() { Set<String> locations = new LinkedHashSet<String>(); // User-configured settings take precedence, so we do them first if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { for (String path : asResolvedSet( this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { path = ResourceUtils.FILE_URL_PREFIX + path; } } locations.add(path); } } // DEFAULT_SEARCH_LOCATIONS:Note the order is from least to most specific (last one wins) // asResolvedSet 会进行逆序操作 locations.addAll(asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; } // 如果有、则用 spring.config.name 属性指定配置文件名, // 否则用默认的配置文件名是 application private Set<String> getSearchNames() { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); } return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); } private Set<String> asResolvedSet(String value, String fallback) { List<String> list = Arrays.asList(StringUtils.trimArrayElements( StringUtils.commaDelimitedListToStringArray(value != null ? this.environment.resolvePlaceholders(value) : fallback))); Collections.reverse(list); return new LinkedHashSet<String>(list); } // load 方法会调用到这里来加载属性源,删除了一些trace日志相关的代码 private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile) throws IOException { Resource resource = this.resourceLoader.getResource(location); PropertySource<?> propertySource = null; if (resource != null && resource.exists()) { String name = "applicationConfig: [" + location + "]"; String group = "applicationConfig: [" + identifier + "]"; // propertiesLoader.load 使用 PropertiesPropertySourceLoader/YamlPropertySourceLoader 对资源进行加载 propertySource = this.propertiesLoader.load (resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null) { handleProfileProperties(propertySource); } } return propertySource; } private void addConfigurationProperties(MutablePropertySources sources) { List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>(); for (PropertySource<?> item : sources) { reorderedSources.add(item); } addConfigurationProperties(new ConfigurationPropertySources(reorderedSources)); } private void addConfigurationProperties(ConfigurationPropertySources configurationSources) { MutablePropertySources existingSources = this.environment.getPropertySources(); if (existingSources.contains(DEFAULT_PROPERTIES)) { // 覆盖默认的属性源 existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources); } else { // 前面加载的比后面加载的优先级高 existingSources.addLast(configurationSources); } }
不同的搜索、加载顺序决定了配置文件的不同优先级:
PropertiesPropertySourceLoader
加载同一个文件名, properties
后缀的比 xml
的先加载,优先级反而低了。 欢迎关注我的微信公众号: coderbee笔记 ,可以更及时回复你的讨论。