SpringBoot加载配置文件
读过SpringBoot源码的同学应该都知道它会在启动过程中根据spring.factories加载监听器,而其中有一个名叫ConfigFileApplicationListener
的监听器,它的作用为加载配置信息,即application.xml、application.yml。
常量值说明 在ConfigFileApplicationListener
定义了一批常量,他们主要为加载配置文件服务,现在就总体地看看这些常量吧。
名称
类型
默认值
说明
DEFAULT_SEARCH_LOCATIONS
String
classpath:/,classpath:/config/,file:./,file:./config/
配置文件加载顺序
DEFAULT_NAMES
String
application
默认配置文件名称
ACTIVE_PROFILES_PROPERTY
String
spring.profiles.active
“活动配置文件”属性名称
INCLUDE_PROFILES_PROPERTY
String
spring.profiles.include
“包含配置文件”属性名称。
CONFIG_NAME_PROPERTY
String
spring.config.name
“配置名称”属性名称。
CONFIG_LOCATION_PROPERTY
String
spring.config.location
“配置位置”属性名称。
DEFAULT_ORDER
int
Integer.MIN_VALUE+10
处理器的默认顺序。
配置文件加载 监听器入口 阅读SpringBoot启动源码时,我们都知道监听器真实生效的方法是onApplicationEvent
,ConfigFileApplicationListener
中的onApplicationEvent
的源码如下:
1 2 3 4 5 6 7 8 9 public void onApplicationEvent (ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }
通过onApplicationEnvironmentPreparedEvent
=>postProcessEnvironment
方法
1 2 3 4 5 6 7 8 9 public void postProcessEnvironment (ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); }
addPropertySources源码如下:
1 2 3 4 5 protected void addPropertySources (ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); new Loader (environment, resourceLoader).load(); }
通过上述代码,可以看出配置文件的接在就在load
方法中。
文件加载 load方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public void load () { this .propertiesLoader = new PropertySourcesLoader (); this .activatedProfiles = false ; this .profiles = Collections.asLifoQueue(new LinkedList <Profile>()); this .processedProfiles = new LinkedList <Profile>(); 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); } } } this .profiles.add(null ); while (!this .profiles.isEmpty()) { Profile profile = this .profiles.poll(); for (String location : getSearchLocations()) { if (!location.endsWith("/" )) { load(location, null , profile); } else { for (String name : getSearchNames()) { load(location, name, profile); } } } this .processedProfiles.add(profile); } addConfigurationProperties(this .propertiesLoader.getPropertySources()); }
查找配置文件路径 值得注意的是getSearchLocations
方法,其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Set<String> getSearchLocations() { Set<String> locations = new LinkedHashSet<String>(); // User-configured settings take precedence, so we do them first // 判断当前环境中是否有spring.config.location属性,如果有的话,则加载spring.config.location指定的配置文件 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); } } //添加默认的配置文件,按照类中定义的顺序加载文件: //其顺序为:classpath:/,classpath:/config/,file:./,file:./config/ locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; }
DEFAULT_SEARCH_LOCATIONS为文件默认加载顺序,其值为classpath:/,classpath:/config/,file:./,file:./config/,然后springboot会按照这几个位置的由后到前的顺序去加载。
通过查看asResolvedSet
的源码:
1 2 3 4 5 6 7 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); }
不难看出,这里对list进行了Collections.reverse
(反转处理)。
即配置文件真正的加载顺序为:
file:./config/
file:./
classpath:/config/
classpath:/
spring.config.location
正式加载配置文件 load(String location, String name, Profile profile)
方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void load (String location, String name, Profile profile) { String group = "profile=" + (profile == null ? "" : profile); if (!StringUtils.hasText(name)) { loadIntoGroup(group, location, profile); } else { for (String ext : this .propertiesLoader.getAllFileExtensions()) { if (profile != null ) { loadIntoGroup(group, location + name + "-" + profile + "." + ext, null ); for (Profile processedProfile : this .processedProfiles) { if (processedProfile != null ) { loadIntoGroup(group, location + name + "-" + processedProfile + "." + ext, profile); } } loadIntoGroup(group, location + name + "-" + profile + "." + ext, profile); } loadIntoGroup(group, location + name + "." + ext, profile); } } }
在Load方法中,首先会通过getAllFileExtensions
方法去组装所有可加载文件的扩展名,然后在通过loadIntoGroup
方法加载配置文件,而我们跟读到loadIntoGroup
中会发现其只执行了 doLoadIntoGroup
方法, doLoadIntoGroup
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile) throws IOException { Resource resource = this .resourceLoader.getResource(location); PropertySource<?> propertySource = null ; StringBuilder msg = new StringBuilder (); if (resource != null && resource.exists()) { String name = "applicationConfig: [" + location + "]" ; String group = "applicationConfig: [" + identifier + "]" ; propertySource = this .propertiesLoader.load(resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null ) { msg.append("Loaded " ); handleProfileProperties(propertySource); } else { msg.append("Skipped (empty) " ); } } else { msg.append("Skipped " ); } msg.append("config file " ); msg.append(getResourceDescription(location, resource)); if (profile != null ) { msg.append(" for profile " ).append(profile); } if (resource == null || !resource.exists()) { msg.append(" resource not found" ); this .logger.trace(msg); } else { this .logger.debug(msg); } return propertySource; }
上诉源码中有一段核心的方法
1 2 3 4 5 6 propertySource = this .propertiesLoader.load(resource, group, name, (profile == null ? null : profile.getName())); if (propertySource != null ) { msg.append("Loaded " ); handleProfileProperties(propertySource); }
如果propertySource存在,则调用handleProfileProperties方法。
handleProfileProperties源码:
1 2 3 4 5 private void handleProfileProperties (PropertySource<?> propertySource) { SpringProfiles springProfiles = bindSpringProfiles(propertySource); maybeActivateProfiles(springProfiles.getActiveProfiles()); addProfiles(springProfiles.getIncludeProfiles()); }
bindSpringProfiles源码:
1 2 3 4 5 private SpringProfiles bindSpringProfiles (PropertySource<?> propertySource) { MutablePropertySources propertySources = new MutablePropertySources (); propertySources.addFirst(propertySource); return bindSpringProfiles(propertySources); }
bindSpringProfiles源码:
1 2 3 4 5 6 7 8 9 private SpringProfiles bindSpringProfiles (PropertySources propertySources) { SpringProfiles springProfiles = new SpringProfiles (); RelaxedDataBinder dataBinder = new RelaxedDataBinder (springProfiles, "spring.profiles" ); dataBinder.bind(new PropertySourcesPropertyValues (propertySources, false )); springProfiles.setActive(resolvePlaceholders(springProfiles.getActive())); springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude())); return springProfiles; }
通过这样一步一步的跟读,我们能发现配置文件的装载主要是通过new PropertySourcesPropertyValues
来完成。
查看PropertySourcesPropertyValues
的构造方法可以发现,它在装载值时调用了这样一个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void processPropertySource (PropertySource<?> source, PropertySourcesPropertyResolver resolver) { if (source instanceof CompositePropertySource) { processCompositePropertySource((CompositePropertySource) source, resolver); } else if (source instanceof EnumerablePropertySource) { processEnumerablePropertySource((EnumerablePropertySource<?>) source, resolver, this .includes); } else { processNonEnumerablePropertySource(source, resolver); } }
配置文件键值对加载方法
此处以yml
文件加载为例
配置文件键值对加载方法,org.springframework.beans.factory.config.YamlProcessor#process(MatchCallback callback, Yaml yaml, Resource resource)
在该方法中通过 yaml.loadAll(reader)
去加载文件的属性,然后在下方process(asMap(object), callback)
通过callback
进行键值对组装。
关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try { for (Object object : yaml.loadAll(reader)) { if (object != null && process(asMap(object), callback)) { count++; if (this .resolutionMethod == ResolutionMethod.FIRST_FOUND) { break ; } } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "" ) + " from YAML resource: " + resource); } } finally { reader.close(); }
该代码中的callback为org.springframework.boot.env.YamlPropertySourceLoader#process()中new的一个,源码如下:
1 2 3 4 5 6 7 8 9 10 public Map<String, Object> process () { final Map<String, Object> result = new LinkedHashMap <String, Object>(); process(new MatchCallback () { @Override public void process (Properties properties, Map<String, Object> map) { result.putAll(getFlattenedMap(map)); } }); return result; }
总结 配置文件的加载主要分为两个步骤:
查找配置文件
加载配置文件中的值
其中查找配置文件加载顺序为:
file:./config/
file:./
classpath:/config/
classpath:/
spring.config.location
spring.config.location为在启动SpringBoot时,为其指定的配置文件路径,
设置方式为:
1 java -jar demo.jar --Dspring.config.location=application.yml