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

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

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

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

    之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void cr ...

  2. Spring Boot 启动(二) Environment 加载

    Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...

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

    Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qia ...

  4. Spring Boot启动过程(七):Connector初始化

    Connector实例的创建已经在Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动中提到了: Connector是LifecycleMBeanBase的子类,先是设置L ...

  5. spring boot / cloud (四) 自定义线程池以及异步处理@Async

    spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...

  6. Spring Boot 启动(二) 配置详解

    Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置 ...

  7. Spring Boot 启动(一) SpringApplication 分析

    Spring Boot 启动(一) SpringApplication 分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html ...

  8. Spring Boot 2 (四):使用 Docker 部署 Spring Boot

    Spring Boot 2 (四):使用 Docker 部署 Spring Boot Docker 技术发展为微服务落地提供了更加便利的环境,使用 Docker 部署 Spring Boot 其实非常 ...

  9. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

随机推荐

  1. 记数据库数据文件损坏恢复ORA-00376+ORA-01110

    现象:业务平台无法登陆,日志报错为ORACLE的错误. 查看oracle日志的报错, ORA-00376: file 5 cannot be read at this time ORA-01110: ...

  2. verilog 介绍

    Verilog HDL Verilog HDL是在C语言的基础上发展起来的一种硬件描述语言,语法较自由.VHDL和Verilog HDL两者相比,VHDL的书写规则比Verilog HDL烦琐一些,但 ...

  3. python3下调用系统massagebox对话框

    #python3下调用系统massagebox对话框#先安装pwin32插件https://github.com/mhammond/pywin32/releases import win32apiim ...

  4. Url的拦截问题

    如果 在中没有, 就会去找欢迎页,先找/index,jsp,在webapp里并没有,就继续往下找/index.html, 是图中的拦截形式,就会被servlet拦截,对应于controller中的请求 ...

  5. linux系统下常用的命令(吐血自己整理,且用且珍惜)

    1)linux命令太多,有时候记不起来是哪个,为了方便大家查询,自己吐血整理了以下这些,转载时请标明出处,珍惜原创成果 吐血自己整理,且用且珍惜) 吐血自己整理,且用且珍惜) 吐血自己整理,且用且珍惜 ...

  6. git commit命令

    git commit 主要是将暂存区里的改动提交到本地的版本库.每次使用git commit 命令我们都会在本地版本库生成一个40位的哈希值,这个哈希值也叫commit-id. commit-id在版 ...

  7. loadrunner飞机订票系统从登陆订票退票登出的脚本实现代码调试通过

    在LR自带的飞机订票系统中,完整模拟一个用户从登陆->订票->退票->登出这样一个业务流程,分解每个事务为一个Action: 进入首页->登陆->进入订票页面->选 ...

  8. 用 ghostscript 转化PDF文件为图片 的参数设置

    example: gswin32 -dSAFER -dBATCH -dNOPAUSE -r300 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -sDEVICE=jp ...

  9. File和FileStream的区别

    File和FileStream的区别直观点:File是一个静态类:FileStream是一个非静态类.File:是一个文件的类,对文件进行操作.其内部封装了对文件的各种操作(MSDN:提供用于创建.复 ...

  10. java课程之团队开发冲刺1.1

    一.今日目标 1.完成课程助手的日期显示以及周数显示功能 2.将功能表改成侧面功能栏 3.将代码重新规范,尽量使得主函数简洁明了