• 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);
    	}
    }
    

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    【工匠大道】svn使用总结
    【工匠大道】将项目同时托管到Github和Git@OSC
    【工匠大道】Git的使用总结
    android中dumpsys命令使用
    loadrunner11使用wplus_init_wsock录制非IE脚本/手机抓包
    互联网协议入门
    jmeter将上一个请求的结果作为下一个请求的参数——使用正则提取器
    使用Fiddler对Android应用进行抓包
    github readme.md添加图片
    python中subprocess.Popen的args和shell参数的使用
  • 原文地址:https://www.cnblogs.com/binarylei/p/10645912.html
Copyright © 2020-2023  润新知