1. Environment 的初始化流程
ConfigFileApplicationListener
收到 ApplicationEnvironmentPreparedEvent
事件后通过 SPI 加载所有的 EnvironmentPostProcessor
实现,触发其 postProcessEnviroment
方法。
SpringApplication.run() ->
SpringFactoriesLoader.loadFactories(ApplicationListener) ->
SpringApplication.prepareEnviroment() -> EventPublishingRunListener.enviromentPrepared(ApplicationEnviromentPraparedEvent) ->
SimpleApplicationEventMulticaster.multicastEvent() ->
ConfigFileApplicationListener.onApplicationOnEnviromentPreparedEvent() ->
EnviromentPostProcessor.postProcessEnviroment()
比较重要的 EnviromentPostProcessor
实现是 HostInfoEnvironmentPostProcessor
和 ConfigFileApplicationListener
。
2. HostInfoEnvironmentPostProcessor.postProcessEnviroment
获取本机的 主机名和IP地址,封装在 PropertySource
添加到 environment 里。
3. ConfigFileApplicationListener.postProcessEnviroment
ConfigFileApplicationListener
自身也实现了 EnvironmentPostProcessor
,通过内部类 Loader 去加载配置文件,其主要流程如下:
- 从 Environment 中获取 active 和 include 的 profile 集合。进行迭代:
- 获取所有的搜索路径,进行迭代,默认的搜索路径是
classpath:/,classpath:/config/,file:./,file:./config/
。 - 如果某个搜索路径不以
/
结尾的则认为是一个文件,直接加载,否则,找出所有的搜索文件名 name 进行迭代搜索,默认的搜索文件名是 “application”。 - 通过
PropertySourcesLoader
找出支持的所有配置文件后缀进行迭代。 - 最终得到
location + name + "-" + profile + "." + ext
组成的一个具体的完整路径,通过PropertiesLoader.load
方法加载该路径指向的配置文件。 PropertiesLoader.load
内部又根据配置文件的后缀用不同的PropertySourceLoader
去加载得到一个PropertySource
。- 对于解析得到的
PropertySource
,找出里面激活的 profile,添加到 proflie 集合里进行迭代。 - 继续迭代下一个 profile 。
3.1 PropertySourceLoader
PropertySourceLoader
是用来加载 PropertySource
的一个策略接口,有两个具体的实现类 PropertiesPropertySourceLoader
和 YamlPropertySourceLoader
,前者用于加载 properties/xml
后缀的配置文件,后者用于加载 yml
后者的配置文件。
3.2 PropertySourcesLoader
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);
}
}
3.3 Loader 加载配置的源码
// 加载属性源到 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笔记,可以更及时回复你的讨论。