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


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

原文地址:https://www.cnblogs.com/binarylei/p/10645912.html

时间: 2024-11-03 22:48:58

Spring Boot 启动(四) EnvironmentPostProcessor的相关文章

Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动

之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServ

Spring Boot启动过程及回调接口汇总

Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qian) 注:本文基于spring-boot 1.4.1.RELEASE, spring 4.3.3.RELEASE撰写. 启动顺序 Spring boot的启动代码一般是这样的: 1 2 3 4 5 6 @SpringBootApplication public class SampleApplica

Spring Boot 启动(二) 配置详解

Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置文件加载顺序 Spring Boot 配置文件加载分析 - ConfigFileApplicationListener 一.Spring Framework 配置 略... 二.Spring Boot 配置 2.1 随机数配置 name.value=${random.int} name.int=${

Spring Boot启动原理解析

Spring Boot启动原理解析http://www.cnblogs.com/moonandstar08/p/6550758.html 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开SpringBoot的神秘面纱,让它不在神秘. 正文 我们开发任何一个Spring Boot项目,都会用到如下的启动类 从上面代码可以看出,Annotation定义(@Sp

spring boot启动原理步骤分析

spring boot最重要的三个文件:1.启动类 2.pom.xml 3.application.yml配置文件 一.启动类->main方法 1.spring boot通过fat jar方式用jdk命令java -jar jarname.jar启动的. fat jar就是包含被引用jar包的jar,因为会包含很多jar包,所以称为fat,肥胖. 2.spring  boot通过static void main方法启动,main方法是java程序总是最先运行的地方,这个是由jvm虚拟机决定的.任

Spring Boot启动过程(三)

我已经很精简了,两篇(Spring Boot启动过程(一).pring Boot启动过程(二))依然没写完,接着来. refreshContext之后的方法是afterRefresh,这名字起的真...好.afterRefresh方法内只调用了callRunners一个方法,这个方法从上下文中获取了所有的ApplicationRunner和CommandLineRunner接口的实现类,并执行这些实现类的run方法.例如Spring Batch的JobLauncherCommandLineRun

spring Boot启动报错Initialization of bean failed; nested exception is java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotatedElementUtils.getAnnotationAttributes

spring boot 启动报错如下 org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure

Spring boot启动成功后输出提示

添加logback-spring.xml,将log输出到文件,控制台输出的level改为error因此只会出处banner src/main/resources/banner.txt的内容为 start... 但是输出完banner后,spring boot并没有启动完毕 因此,我想在Spring boot启动成功后输出提示 有两种方式 1.实现 ApplicationRunnerImpl eg: package com.example.demo.configure; import org.sp

Spring Boot 启动源码解析系列六:执行启动方法一

1234567891011121314151617181920212223242526272829303132333435363738394041424344 public ConfigurableApplicationContext (String... args) { StopWatch stopWatch = new StopWatch(); // 开始执行,记录开始时间 stopWatch.start(); ConfigurableApplicationContext context =