Yaml是目前常用的一种软件参数存储格式,用于应对软件运行时调整参数的需求。本文介绍将yaml文件解析成对象的几种方法。
YAML小知识
YAML的全称是“YAML Ain't Markup Language”,是一个对人类阅读友好的数据序列化格式,几乎所有编程语言都有支持。
YAML的在册媒体类型是“application/yaml
”(RFC9512),常用的文件扩展名是yaml
或yml
。
本文的所有例子在java8
、org.yaml:snakeyaml:1.30
,spring-boot 2.7.18
环境下测试通过。
方式1:Snak Yaml
Snake yaml 是在Java中解析YAML文件必用的库。本文介绍的其他方法也都依赖Snak yaml。
Snake yaml本身就支持将yaml转换成java bean规范的对象。
来看这个例子:
- Yaml文件
user:
name: "root"
age: 18
app:
name: "springboot"
version: "1.0.0"
- 数据类
public class App {
public String name;
public String version;
}
public class User {
String name;
int age;
int birthYear;
//省略 get set
}
public class ConfigFile {
User user;
App app;
//省略get set;
}
于是,可以使用Yaml.loadAs
方法来将数据转换成对象:
public class LoadYaml {
public static final Path application_yml = Paths.get("src/main/resources/application.yml");
Yaml yaml = new Yaml();
@Test
void yaml_to_dto() throws Exception {
ConfigFile config = yaml.loadAs(Files.newInputStream(application_yml), ConfigFile.class);
assertThat(config).isNotNull();
assertThat(config.user).isNotNull();
assertThat(config.user.getName()).isEqualTo("root");
assertThat(config.app).isNotNull();
}
}
方式2:Jackson
第二种方法是使用jackson。jackson加载yaml扩展后,就能支持从yaml到对象的转换。并且这样的好处是可以使用jackson的注解来调整映射行为,比直接使用Yaml.loadAs
功能要多。
@Test
void parseYaml() throws Exception {
ConfigFile config = new ObjectMapper(new YAMLFactory())
.readerFor(ConfigFile.class)
.readValue(application_yml.toFile());
assertThat(config).isNotNull();
assertThat(config.user).isNotNull();
assertThat(config.user.getName()).isEqualTo("root");
assertThat(config.app).isNotNull();
assertThat(config.app.name).isNotNull();
}
提醒一下,有个缺陷,尚不支持含有anchor的yaml转换成对象。相应的问题跟踪:https://github.com/FasterXML/jackson-dataformats-text/issues/98
方式3:Spring Boot
第三种方式是使用spring boot中提供的bind工具,好处是在类型的转换上更灵活,映射处理上更完善。
@Test
void parseYaml() throws Exception {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new FileSystemResource(application_yml));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
User u = binder.bind("user", User.class).get();
assertThat(u.getName()).isEqualTo("root");
}
注意 spring boot 默认不支持public field bind。但支持relax name bind,例如在yaml中写 birth-year: 2023
可以自动映射到 user.setBirthYear
方法上。
注意2,spring boot的处理逻辑与第1、2种是有明显差异的,前述2种方法大体上都是tree→object直接转换,但spring boot是tree→properties→object,中间多了一步将tree展开成单层hash map的过程。
小结
方法 | Java Bean | Public Fields | 差异 |
---|---|---|---|
Snak Yaml | √ | √ | 未详尽测试,推测功能简单 |
Jackson | √ | √ | 可以使用 jackson 注解来调整构造过程 |
Spring Boot | √ | × | 支持 relax name bind、类型检查、支持类型转换 |
除了yaml以外,ini
、properties
、toml
都可以用来存储参数。除了文件存储以外,还可以使用配置中心(如nacos),关系数据库也可以,根据需要而选择。