在 SpringBoot 中,从 application.properties 或 application.yaml 文件中读取配置文件是非常常用的功能。
本文介绍的是从 JSON 文件中读取配置,而不是从 .properties 文件或 .yaml 文件中读取配置。
如果你有用 .json 文件代替 .properties 或 .yaml 文件读取配置的需求,或者你想了解一下如何扩展 SpringBoot 默认的配置读取方式,那可以看看本文。
总有那么一些奇葩的需求可能会用到,比如:
.json文件当然能代替.yaml文件。
超集的意思就是: .yaml 能表达的语义 .json 一定能表达,但是反过来不一定。
总之就是 .json 文件一定能表达 .yaml 的语义。
看看下面三个配置文件:
xxx:
config:
data:
datasource-list:
- datasource-name: : ds-01
url: jdbc:ds-01-xxxx
password: pwd-for-ds-01
username: username-for-ds-01
driver-class-name: com.mysql.cj.jdbc.Driver
- datasource-name: : ds-02
url: jdbc:ds-02-xxxx
password: pwd-for-ds-02
username: username-for-ds-02
driver-class-name: com.mysql.cj.jdbc.NonRegisteringDriverxxx.config.data.datasource-list[0].datasource-name=ds-01
xxx.config.data.datasource-list[0].url=jdbc:ds-01-xxxx
xxx.config.data.datasource-list[0].password=pwd-for-ds-01
xxx.config.data.datasource-list[0].username=username-for-ds-01
xxx.config.data.datasource-list[0].driver-class-name=com.mysql.cj.jdbc.Driver
xxx.config.data.datasource-list[1].datasource-name=ds-02
xxx.config.data.datasource-list[1].url=jdbc:ds-02-xxxx
xxx.config.data.datasource-list[1].password=pwd-for-ds-02
xxx.config.data.datasource-list[1].username=username-for-ds-02
xxx.config.data.datasource-list[1].driver-class-name=com.mysql.cj.jdbc.NonRegisteringDriver{
"xxx": {
"config": {
"data": {
"datasource-list": [
{
"datasource-name": "ds-01",
"url": "jdbc:ds-01-xxxx",
"password": "pwd-for-ds-01",
"username": "username-for-ds-01",
"driver-class-name": "com.mysql.cj.jdbc.Driver"
},
{
"datasource-name": "ds-02",
"url": "jdbc:ds-02-xxxx",
"password": "pwd-for-ds-02",
"username": "username-for-ds-02",
"driver-class-name": "com.mysql.cj.jdbc.NonRegisteringDriver"
}
]
}
}
}
}不难发现,上面三种格式的配置文件,其实表达的配置语义是等价的。
yaml 文件的缩进在 少量配置 的情况下看着非常清晰,但是当配置非常多、嵌套非常深的情况下就显得有点乱了。
所以本文介绍一种可以使用 application.json 文件代替 application.yaml 的方式。
默认情况下 Spring 会将 application.properties 或 application.yaml 封装为 PropertySource,最终以 分层的方式加入到 org.springframework.core.env.Environment(以下简称 Environment) 中。
如上如所示:在 SpringBoot 中能读取的所有配置都是从 Environment 中读取的。
在 spring 源码的 org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
// ...
@Nullable
protected T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 从上到下 逐个遍历 PropertySource
// 哪个 PropertySource 能获取到 key 就返回
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
// ...
} spring 获取配置的方式就是:
也就是类似于 Docker 分层镜像的思想,上层覆盖下层。多个相同的 key ,靠前的优先。
那我们扩展 .json 文件就可以从这里入手了:
经过上面的分析,通过 ConfigurableEnvironment 结合 org.springframework.boot.env.EnvironmentPostProcessor 就可以实现 .json 文件的支持了。
但是,本文不打算直接修改 ConfigurableEnvironment,因为有一个更简单的方式就是 扩展 org.springframework.boot.env.PropertySourceLoader。
顾名思义 org.springframework.boot.env.PropertySourceLoader 中的 PropertySourceLoader 就是来加载 PropertySource的 ,扩展 .json 文件的需求,我们直接实现 PropertySourceLoader 接口就行了,而没必要通过 ConfigurableEnvironment 来修改 PropertySource,直接修改 PropertySource 的话还需要考虑 多环境支持的问题,而通过 PropertySourceLoader 的话就无需考虑多环境支持了(SpringBoot会替我们搞定)。
其实核心就下面几行代码,自定义一个能加载 JSON 文件的 JsonPropertySourceLoader:
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[]{"json", "json5"};
}
@Override
public List> load(String name, Resource resource) throws IOException {
try (final InputStream inputStream = resource.getInputStream()) {
// application.json 的内容读取到字符串 jsonString 中
final String jsonString = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// 将 jsonString 字符串转为 Map
final Map map = JsonFlattener.flattenAsMap(jsonString);
// 从 Map 构建一个 PropertySource
final MapPropertySource propertySource = new MapPropertySource(name, map);
return List.of(propertySource);
}
}
} 但是别忘了将我们自定义的 JsonPropertySourceLoader 加入到 spring 的 SPI 文件 spring.factories 中:
@Data
@ConfigurationProperties(prefix = "xxx.config.data")
public class MyDataConfig {
private List datasourceList = new ArrayList<>();
@Data
public static class DatasourceConfig {
private String datasourceName;
private String url;
private String username;
private String password;
private Class<? extends Driver> driverClassName;
}
} @RestController
@EnableConfigurationProperties({MyDataConfig.class})
public class MyDataConfigController {
private final MyDataConfig myDataConfig;
public MyDataConfigController(MyDataConfig myDataConfig) {
this.myDataConfig = myDataConfig;
}
@GetMapping("/my-data-config")
public MyDataConfig myDataConfig() {
return this.myDataConfig;
}
}好了,现在已经可以支持 application.json 文件了。其实关键类就是 扩展了 PropertySourceLoader 而已。
顺便引出了 SpringBoot 分层配置的思想供读者自己去探索。
如果有必要的话,我可以在再写一篇专门讲解 spring 的 Environment 的文章。
| 留言与评论(共有 0 条评论) “” |