SpringBoot通过事件广播机制通知ConfigFileApplicationListener这个监听器来加载properties和yaml文件。关于SpringBoot事件编程模型可参考:SpringBoot事件编程模型解析。
SpringBoot一站式启动过程中,会经过环境准备阶段:
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
该过程中,会向监听者广播环境准备完毕的事件:
listeners.environmentPrepared(environment);
其中ConfigFileApplicationListener监听者接收到该事件后, 会委托EnvionmentPostProcesor环境后置处理器来加载配置文件,这里的ConfigFileApplicationListener的另外一个角色正好是环境后置处理器,所以最终文件加载还是在ConfigFileApplicationListener中完成的。
//继承了环境后置处理器和应用程序监听器
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
...
}
ConfigFileApplicationListener中加载配置文件总领代码如下:
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
加载规则如下:先看命令行中是否指定spring.config.location,如果有,则加载指定地址配置文件,如果没有,则按classpath:/,classpath:/config/,file:./,file:./config/优先级加载该路径下的配置文件,可配置spring.profiles.active或者spring.profiles.include加载application-xxx.properties文件,也可配置spring.config.additional-location加载其他路径下的配置文件。
property和yaml具体的资源加载器分别是:PropertiesPropertySourceLoader、YamlPropertySourceLoader,将配置文件读取到内存其实就是JAVA IO的过程,以classpath路径下配置文件加载为例:
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
private static final String XML_FILE_EXTENSION = ".xml";
@Override
public String[] getFileExtensions() {
return new String[] { "properties", "xml" };
}
@Override
public List> load(String name, Resource resource)
throws IOException {
Map properties = loadProperties(resource);
if (properties.isEmpty()) {
return Collections.emptyList();
}
return Collections
.singletonList(new OriginTrackedMapPropertySource(name, properties));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
//加载
return new OriginTrackedPropertiesLoader(resource).load();
}
}
load过程如下:
public Mapload(boolean expandLists) throws IOException { //通过ClasspathResource中获取字符流 try (CharacterReader reader = new CharacterReader(this.resource)) { Map result = new linkedHashMap<>(); StringBuilder buffer = new StringBuilder(); while (reader.read()) { String key = loadKey(buffer, reader).trim(); if (expandLists && key.endsWith("[]")) { key = key.substring(0, key.length() - 2); int index = 0; do { OriginTrackedValue value = loadValue(buffer, reader, true); put(result, key + "[" + (index++) + "]", value); if (!reader.isEndOfLine()) { reader.read(); } } while (!reader.isEndOfLine()); } else { OriginTrackedValue value = loadValue(buffer, reader, false); put(result, key, value); } } return result; } }
从文件到输入流:
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}



