Spring Boot 啟動(四) EnvironmentPostProcessor


Spring Boot 啟動(四) EnvironmentPostProcessor

Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10198698.html)

  1. Spring Boot 配置使用
  2. Spring Boot 配置文件加載流程分析 - ConfigFileApplicationListener
  3. Spring Boot 配置文件加載 - EnvironmentPostProcessor

一、EnvironmentPostProcessor

ConfigFileApplicationListener 是 Spring Boot 中處理配置文件的監聽器。

// ConfigFileApplicationListener
private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	// 1. 委托給 EnvironmentPostProcessor 處理,也是通過 spring.factories 配置
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	// 2. ConfigFileApplicationListener 本身也實現了 EnvironmentPostProcessor 接口
	postProcessors.add(this);
	// 3. spring 都都通過 AnnotationAwareOrderComparator 控制執行順序
	AnnotationAwareOrderComparator.sort(postProcessors);
	// 4. 執行 EnvironmentPostProcessor
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
	}
}

在 spring.factories 配置文件中默認定義了三個 EnvironmentPostProcessor 的實現類:

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

優先級 SystemEnvironmentPropertySourceEnvironmentPostProcessor > SpringApplicationJsonEnvironmentPostProcessor

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor 對 systemEnvironment 屬性進行了包裝。
  • SpringApplicationJsonEnvironmentPostProcessor 解析 spring.application.json 或 SPRING_APPLICATION_JSON 配置的 json 字符串。

本節專門介紹一下 SystemEnvironmentPropertySourceEnvironmentPostProcessor 如何解析 JSON 格式的。

二、spring.application.json 使用

spring.application.json 或 SPRING_APPLICATION_JSON 定義的 json 字符串。命令行配置如下:

java -jar xxx.jar --spring.application.json='{"foo":"bar"}'

編程方式如下:

@Test
public void list() {
	assertThat(this.environment.resolvePlaceholders("${foo[1]:}")).isEmpty();
	TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
			"SPRING_APPLICATION_JSON={\"foo\":[\"bar\",\"spam\"]}");
	this.processor.postProcessEnvironment(this.environment, null);
	assertThat(this.environment.resolvePlaceholders("${foo[1]:}")).isEqualTo("spam");
}

三、源碼分析

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
		SpringApplication application) {
	MutablePropertySources propertySources = environment.getPropertySources();
	// environment 中定義的 spring.application.json 或 SPRING_APPLICATION_JSON 解析成 JsonPropertySource
	// 默認只會解析第一個配置的 json 字符串
	propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull)
			.findFirst().ifPresent((v) -> processJson(environment, v));
}

private void processJson(ConfigurableEnvironment environment,
		JsonPropertyValue propertyValue) {
	JsonParser parser = JsonParserFactory.getJsonParser();
	Map<String, Object> map = parser.parseMap(propertyValue.getJson());
	if (!map.isEmpty()) {
		addJsonPropertySource(environment,
				new JsonPropertySource(propertyValue, flatten(map)));
	}
}

這里我們還需要注意 JsonPropertySource 的讀取順序。spring.application.json 的級別非常高,只低於命令行配置。

private void addJsonPropertySource(ConfigurableEnvironment environment,
		PropertySource<?> source) {
	MutablePropertySources sources = environment.getPropertySources();
	String name = findPropertySource(sources);
	if (sources.contains(name)) {
		sources.addBefore(name, source);
	} else {
		sources.addFirst(source);
	}
}
// 默認會放到 jndiProperties 或 systemProperties 之前
private String findPropertySource(MutablePropertySources sources) {
	if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && sources
			.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
		return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;

	}
	return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
}

3.1 map 解析成 properties 分析

這里可以看到 Spring 將 map 轉為 properties 的代碼十分簡潔。

// "SPRING_APPLICATION_JSON={\"foo\":[\"bar\",\"spam\"]}" -> ${foo[1]:}=spam
// "SPRING_APPLICATION_JSON={\"foo.bar\":\"spam\"}" -> ${foo.bar:}=spam
// "SPRING_APPLICATION_JSON={\"foo\":{\"bar\":\"spam\",\"rab\":\"maps\"}}" -> ${foo.bar:}=spam
private Map<String, Object> flatten(Map<String, Object> map) {
	Map<String, Object> result = new LinkedHashMap<>();
	flatten(null, result, map);
	return result;
}

private void flatten(String prefix, Map<String, Object> result,
		Map<String, Object> map) {
	String namePrefix = (prefix != null) ? prefix + "." : "";
	map.forEach((key, value) -> extract(namePrefix + key, result, value));
}

private void extract(String name, Map<String, Object> result, Object value) {
	if (value instanceof Map) {
		flatten(name, result, (Map<String, Object>) value);
	} else if (value instanceof Collection) {
		int index = 0;
		for (Object object : (Collection<Object>) value) {
			extract(name + "[" + index + "]", result, object);
			index++;
		}
	} else {
		result.put(name, value);
	}
}

每天用心記錄一點點。內容也許不重要,但習慣很重要!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM