Spring Boot 启动(四) EnvironmentPostProcessor
Spring Boot 启动(四) EnvironmentPostProcessor
Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)
- Spring Boot 配置使用
- Spring Boot 配置文件加载流程分析 - ConfigFileApplicationListener
- 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的更多相关文章
- Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动
之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void cr ...
- Spring Boot 启动(二) Environment 加载
Spring Boot 启动(二) Environment 加载 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 上一节中 ...
- Spring Boot启动过程及回调接口汇总
Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qia ...
- Spring Boot启动过程(七):Connector初始化
Connector实例的创建已经在Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动中提到了: Connector是LifecycleMBeanBase的子类,先是设置L ...
- spring boot / cloud (四) 自定义线程池以及异步处理@Async
spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...
- Spring Boot 启动(二) 配置详解
Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置 ...
- Spring Boot 启动(一) SpringApplication 分析
Spring Boot 启动(一) SpringApplication 分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html ...
- Spring Boot 2 (四):使用 Docker 部署 Spring Boot
Spring Boot 2 (四):使用 Docker 部署 Spring Boot Docker 技术发展为微服务落地提供了更加便利的环境,使用 Docker 部署 Spring Boot 其实非常 ...
- Spring Boot(十四):spring boot整合shiro-登录认证和权限管理
Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...
随机推荐
- sublime编译javaScript脚本
处理步骤: 1. 首先到 nodejs.org 下载 Node.js 安装包并安装.2. 打开 Sublime Text 3 编辑器.选择菜单 Tools --> Build System -- ...
- tornado框架设置
路由 import tornado.ioloop #开启循环 让服务器一直等待请求的到来 import tornado.web #框架基本功能封装在此模块 #例子 class MainHendler( ...
- 文本建模、文本分类相关开源项目推荐(Pytorch实现)
Awesome-Repositories-for-Text-Modeling repo paper miracleyoo/DPCNN-TextCNN-Pytorch-Inception Deep Py ...
- 【读书笔记】segment routing mpls数据平面-1
- 编译安装redis4.0
下载redis4.0的安装包:http://download.redis.io/releases/redis-4.0.11.tar.gz 这里用的是已经下载到电脑上,只需上传即可 解压缩 [root@ ...
- Kickstart自动化安装平台
PXE(preboot execute environment,预启动执行环境)是由Intel公司开发的最新技术,工作于Client/Server的网络模式,支持工作站通过网络从远端服务器下载映像,并 ...
- 简单搭建一个SpringBoot
1.SpringBoot下载 https://start.spring.io/ 选择工程类型,编译语言,版本,工程名称,需要支持组件等:选择好了以后点击生成项目. 之后会下载一个压缩文件,解压之后导入 ...
- splice的用法
splice向数组中删除/添加新元素,然后返回新数组 arrObject.splice(index,howmany,item1,...,itemx); 参数 描述 index 必需.整数,规定添加/删 ...
- CentOS7+CDH5.14.0安装CDH错误排查:该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系
主机错误: 该主机与 Cloudera Manager Server 失去联系的时间过长. 该主机未与 Host Monitor 建立联系 解决办法: 首先查看该主机NTP服务是否启动:https:/ ...
- java基础 ----- 程序的调试
--- -- 什么是程序调试 当程序出错时,我们希望可以这样 逐条语句执行程序 ----- 观察程序的执行情况 ------ 发现问题 ----- 解决问题 但是,程序一闪就运行结束 ...