Springboot漫游日志(22)

Springboot漫游日志(22)

上次看到【ConfigFileApplicationListener】作为一个【EnvironmentPostProcessor】
第504行的【load】方法。

ApplicationEnvironmentPreparedEvent 事件的listener(三)

  • FileEncodingApplicationListener
  • AnsiOutputApplicationListener
  • ConfigFileApplicationListener
  • ClasspathLoggingApplicationListener
  • LoggingApplicationListener

ConfigFileApplicationListener

ConfigFileApplicationListener

private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
		DocumentConsumer consumer) {
	Resource[] resources = getResources(location);
	for (Resource resource : resources) {
		try {
			if (resource == null || !resource.exists()) {
				if (this.logger.isTraceEnabled()) {
					StringBuilder description = getDescription("Skipped missing config ", location, resource,
							profile);
					this.logger.trace(description);
				}
				continue;
			}
			if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
				if (this.logger.isTraceEnabled()) {
					StringBuilder description = getDescription("Skipped empty config extension ", location,
							resource, profile);
					this.logger.trace(description);
				}
				continue;
			}
			if (resource.isFile() && hasHiddenPathElement(resource)) {
				if (this.logger.isTraceEnabled()) {
					StringBuilder description = getDescription("Skipped location with hidden path element ",
							location, resource, profile);
					this.logger.trace(description);
				}
				continue;
			}
			String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
			List<Document> documents = loadDocuments(loader, name, resource);
			if (CollectionUtils.isEmpty(documents)) {
				if (this.logger.isTraceEnabled()) {
					StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
							profile);
					this.logger.trace(description);
				}
				continue;
			}
			List<Document> loaded = new ArrayList<>();
			for (Document document : documents) {
				if (filter.match(document)) {
					addActiveProfiles(document.getActiveProfiles());
					addIncludedProfiles(document.getIncludeProfiles());
					loaded.add(document);
				}
			}
			Collections.reverse(loaded);
			if (!loaded.isEmpty()) {
				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) {
			StringBuilder description = getDescription("Failed to load property source from ", location,
					resource, profile);
			throw new IllegalStateException(description.toString(), ex);
		}
	}
}

再贴一波,location的可能取值

  • file:./config/
  • file:./config/*/
  • file:./
  • classpath:/config/
  • classpath:/

name的取值为【application】

一步一看。

private Resource[] getResources(String location) {
	try {
		if (location.contains("*")) {
			return getResourcesFromPatternLocation(location);
		}
		return new Resource[] { this.resourceLoader.getResource(location) };
	}
	catch (Exception ex) {
		return EMPTY_RESOURCES;
	}
}

如果路径中含有星星,走【getResourcesFromPatternLocation】
显然只有【file:./config/*/】是含有星星的
看一看。

private Resource[] getResourcesFromPatternLocation(String location) throws IOException {
	String directoryPath = location.substring(0, location.indexOf("*/"));
	Resource resource = this.resourceLoader.getResource(directoryPath);
	File[] files = resource.getFile().listFiles(File::isDirectory);
	if (files != null) {
		String fileName = location.substring(location.lastIndexOf("/") + 1);
		Arrays.sort(files, FILE_COMPARATOR);
		return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName)))
				.filter(Objects::nonNull).flatMap((Function<File[], Stream<File>>) Arrays::stream)
				.map(FileSystemResource::new).toArray(Resource[]::new);
	}
	return EMPTY_RESOURCES;
}

先把*/之前的截取了。
【file:./config/*/】就截取成了【file:./config/】
然后找到这个路径下的所有文件,只要文件夹,其他不要。
如果一个都没有,那就返回【EMPTY_RESOURCES】

private static final Resource[] EMPTY_RESOURCES = {};

这里的location是已经拼接后的location
比如:file:./config/*/application-default.yml
所以截取最后一个斜杠之后的内容,就是拿到了【application-default.yml】
然后把文件夹排序,排序规则比较绝对路径,这个是啥效果,先不看。
接着遍历每个文件夹,找到里面和【application-default.yml】名字一样的文件
一维数组变二维数组
然后过滤掉不是null的文件数组
然后把二维数组整合成一维数组
然后对一维数组的每个元素调用【FileSystemResource】的构造方法,转换成Resource
然后得到一个Resource数组,返回。

总结就是,找到config文件夹下子文件夹里面和拼接的配置文件名字一样的文件
读取到内存了。
这时候应该还没有把配置内容解析。
回过头,看上一层方法的返回值
如果没有包含星星的路径

return new Resource[] { this.resourceLoader.getResource(location) };

就看一下默认的resourceLoader【DefaultResourceLoader】
因为Loader构造方法里面已经赋值了

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	this.environment = environment;
	this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
	this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
	this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

往上溯源,拿到的是【application.getResourceLoader()】
打了个断点看了一下,这是一个null

/**
 * Create a new DefaultResourceLoader.
 * @param classLoader the ClassLoader to load class path resources with, or {@code null}
 * for using the thread context class loader at the time of actual resource access
 */
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
	this.classLoader = classLoader;
}

接着看加载location

@Override
public Resource getResource(String location) {
	Assert.notNull(location, "Location must not be null");

	for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
		Resource resource = protocolResolver.resolve(location, this);
		if (resource != null) {
			return resource;
		}
	}

	if (location.startsWith("/")) {
		return getResourceByPath(location);
	}
	else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
		return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
	}
	else {
		try {
			// Try to parse the location as a URL...
			URL url = new URL(location);
			return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
		}
		catch (MalformedURLException ex) {
			// No URL -> resolve as resource path.
			return getResourceByPath(location);
		}
	}
}

第一步遍历的【getProtocolResolvers()】就是空集合了
接着看
如果路径以斜杠开头
配置中都木有,就不看了

String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
public static final String CLASSPATH_URL_PREFIX = "classpath:";

就是以classpath开头
返回【ClassPathResource】
如果是file,就返回【FileUrlResource】,不是就返回【UrlResource】

一路返回到【ConfigFileApplicationListener】506行。
遍历拿到的【resources】
根据不同的情况,打印不同的日志,都是trace级别,可以把spring的日志级别设置为trace就可以看到这些日志。

Springboot漫游日志(22)

然后到533行,到541行。
正菜来了。

private List<Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource)
				throws IOException {
	DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
	List<Document> documents = this.loadDocumentsCache.get(cacheKey);
	if (documents == null) {
		List<PropertySource<?>> loaded = loader.load(name, resource);
		documents = asDocuments(loaded);
		this.loadDocumentsCache.put(cacheKey, documents);
	}
	return documents;
}

从缓存中获取documents,当然是没有的。
所以进入if里面。
loader.load()的loader类型是【PropertySourceLoader】
这是一个接口,是之前从spring.properties里面加载的。
总共有两个实现类。

  • PropertiesPropertySourceLoader
  • YamlPropertySourceLoader

他们有支持的加载类型。
【PropertiesPropertySourceLoader】支持【properties】【xml】
【YamlPropertySourceLoader】支持【yml】【yaml】

至于里面的load方法就不看了,看了头皮发麻。
只贴一下返回值

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<PropertySource<?>> load(String name, Resource resource) throws IOException {
		Map<String, ?> properties = loadProperties(resource);
		if (properties.isEmpty()) {
			return Collections.emptyList();
		}
		return Collections
				.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Map<String, ?> 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();
	}

}
public class YamlPropertySourceLoader implements PropertySourceLoader {

	@Override
	public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}

	@Override
	public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
		if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
			throw new IllegalStateException(
					"Attempted to load " + name + " but snakeyaml was not found on the classpath");
		}
		List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
		if (loaded.isEmpty()) {
			return Collections.emptyList();
		}
		List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
		for (int i = 0; i < loaded.size(); i++) {
			String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
			propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,
					Collections.unmodifiableMap(loaded.get(i)), true));
		}
		return propertySources;
	}

}

返回值都是【OriginTrackedMapPropertySource】
到这里,application.properties里面的内容就被加载到spring里面了,当然了,是被某一次循环加入的。
回到【ConfigFileApplicationListener】543行。
到550行。

List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
	if (filter.match(document)) {
		addActiveProfiles(document.getActiveProfiles());
		addIncludedProfiles(document.getIncludeProfiles());
		loaded.add(document);
	}
}

过滤掉已经加载的文件。
然后给属性赋值,是【ConfigFileApplicationListener】里面的属性。

void addActiveProfiles(Set<Profile> profiles) {
	if (profiles.isEmpty()) {
		return;
	}
	if (this.activatedProfiles) {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied");
		}
		return;
	}
	this.profiles.addAll(profiles);
	if (this.logger.isDebugEnabled()) {
		this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles));
	}
	this.activatedProfiles = true;
	removeUnprocessedDefaultProfiles();
}
private void addIncludedProfiles(Set<Profile> includeProfiles) {
	LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
	this.profiles.clear();
	this.profiles.addAll(includeProfiles);
	this.profiles.removeAll(this.processedProfiles);
	this.profiles.addAll(existingProfiles);
}

接着往下看

Collections.reverse(loaded);
if (!loaded.isEmpty()) {
	loaded.forEach((document) -> consumer.accept(profile, document));
	if (this.logger.isDebugEnabled()) {
		StringBuilder description = getDescription("Loaded config file ", location, resource,
				profile);
		this.logger.debug(description);
	}
}

这里都是函数回调。
遍历loaded集合,然后把document加入到某个地方,看一下consumer是个啥。
他是从【ConfigFileApplicationListener】350行的load方法传入的。

load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));

第三个参数就是,之前已经看过了。

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());
		addMethod.accept(merged, document.getPropertySource());
	};
}

就是return返回的匿名方法,第一个参数是profile,第二个是document。
checkForExisting是false,就直接看下面的。
从loaded里面找到以profile为键的值,找不到就新new一个【MutablePropertySources】
【addMethod】是【MutablePropertySources::addLast】,也是一个回调方法。
再贴一波代码。

/**
 * Add the given property source object with lowest precedence.
 */
public void addLast(PropertySource<?> propertySource) {
	synchronized (this.propertySourceList) {
		removeIfPresent(propertySource);
		this.propertySourceList.add(propertySource);
	}
}

这里document被加入了【propertySourceList】
这里的document其实是【OriginTrackedMapPropertySource】
回到【ConfigFileApplicationListener】553行。
到这里,配置文件就被加载完毕了。

上一篇:android – 如何将Joda Instant转换为LocalDate,反之亦然?


下一篇:初始Vue3.0(8)——模块化-useURLLoader