在上一篇文章中,我们从原始码角度分析了SpringBoot决议yml组态档的全流程,那么我们今天就来点实战,总结一下除了烂大街的@Value
和@ConfigurationProperties
外,还能够通过哪些方式,来读取yml组态档的内容,
1、Environment
在Spring中有一个类Environment
,它可以被认为是当前应用程序正在运行的环境,它继承了PropertyResolver
界面,因此可以作为一个属性决议器使用,先创建一个yml档案,属性如下:
person:
name: hydra
gender: male
age: 18
使用起来也非常简单,直接使用@Autowired
就可以注入到要使用的类中,然后呼叫它的getProperty()
方法就可以根据属性名称取出对应的值了,
@RestController
public class EnvironmentController {
@Autowired
private Environment environment;
@GetMapping("envTest")
private void getEnv(){
System.out.println(environment.getProperty("person.name"));
System.out.println(environment.getProperty("person.gender"));
Integer autoClose = environment
.getProperty("person.age", Integer.class);
System.out.println(autoClose);
String defaultValue = https://www.cnblogs.com/trunks2008/archive/2022/01/21/environment
.getProperty("person.other", String.class, "defaultValue");
System.out.println(defaultValue);
}
}
在上面的例子中可以看到,除了简单的获取外,Environment
提供的方法还可以对取出的属性值进行型别转换、以及默认值的设定,呼叫一下上面的界面,打印结果如下:
hydra
male
18
defaultValue
除了获取属性外,还可以用来判断激活的组态档,我们先在application.yml
中激活pro
档案:
spring:
profiles:
active: pro
可以通过acceptsProfiles
方法来检测某一个组态档是否被激活加载,或者通过getActiveProfiles
方法拿到所有被激活的组态档,测验界面:
@GetMapping("getActiveEnv")
private void getActiveEnv(){
System.out.println(environment.acceptsProfiles("pro"));
System.out.println(environment.acceptsProfiles("dev"));
String[] activeProfiles = environment.getActiveProfiles();
for (String activeProfile : activeProfiles) {
System.out.println(activeProfile);
}
}
打印结果:
true
false
pro
2、YamlPropertiesFactoryBean
在Spring中还可以使用YamlPropertiesFactoryBean
来读取自定义配置的yml档案,而不用再被拘束于application.yml
及其激活的其他组态档,
在使用程序中,只需要通过setResources()
方法设定自定义yml组态档的存盘路径,再通过getObject()
方法获取Properties
物件,后续就可以通过它获取具体的属性,下面看一个例子:
@GetMapping("fcTest")
public void ymlProFctest(){
YamlPropertiesFactoryBean yamlProFb = new YamlPropertiesFactoryBean();
yamlProFb.setResources(new ClassPathResource("application2.yml"));
Properties properties = yamlProFb.getObject();
System.out.println(properties.get("person2.name"));
System.out.println(properties.get("person2.gender"));
System.out.println(properties.toString());
}
查看运行结果,可以读取指定的application2.yml
的内容:
susan
female
{person2.age=18, person2.gender=female, person2.name=susan}
但是这样的使用中有一个问题,那就是只有在这个界面的请求中能够取到这个属性的值,如果再写一个界面,不使用YamlPropertiesFactoryBean
读取组态档,即使之前的方法已经读取过这个yml档案一次了,第二个界面取到的仍然还是空值,来对这个程序进行一下测验:
@Value("${person2.name:null}")
private String name;
@Value("${person2.gender:null}")
private String gender;
@GetMapping("fcTest2")
public void ymlProFctest2(){
System.out.println(name);
System.out.println(gender);
}
先呼叫一次fcTest
界面,再呼叫fcTest2
界面时会打印null
值:
null
null
想要解决这个问题也很简单,可以配合PropertySourcesPlaceholderConfigurer
使用,它实作了BeanFactoryPostProcessor
界面,也就是一个bean工厂后置处理器的实作,可以将组态档的属性值加载到一个Properties
档案中,使用方法如下:
@Configuration
public class PropertyConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer
= new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yamlProFb
= new YamlPropertiesFactoryBean();
yamlProFb.setResources(new ClassPathResource("application2.yml"));
configurer.setProperties(yamlProFb.getObject());
return configurer;
}
}
再次呼叫之前的界面,结果如下,可以正常的取到application2.yml
中的属性:
susan
female
除了使用YamlPropertiesFactoryBean
将yml决议成Properties
外,其实我们还可以使用YamlMapFactoryBean
决议yml成为Map
,使用方法非常类似:
@GetMapping("fcMapTest")
public void ymlMapFctest(){
YamlMapFactoryBean yamlMapFb = new YamlMapFactoryBean();
yamlMapFb.setResources(new ClassPathResource("application2.yml"));
Map<String, Object> map = yamlMapFb.getObject();
System.out.println(map);
}
打印结果:
{person2={name=susan, gender=female, age=18}}
3、监听事件
在上篇介绍原理的文章中,我们知道SpringBoot是通过监听事件的方式来加载和决议的yml档案,那么我们也可以仿照这个模式,来加载自定义的组态档,
首先,定义一个类实作ApplicationListener
界面,监听的事件型别为ApplicationEnvironmentPreparedEvent
,并在构造方法中传入要决议的yml文件名:
public class YmlListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private String ymlFilePath;
public YmlListener(String ymlFilePath){
this.ymlFilePath = ymlFilePath;
}
//...
}
自定义的监听器中需要实作界面的onApplicationEvent()
方法,当监听到ApplicationEnvironmentPreparedEvent
事件时会被触发:
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
ResourceLoader loader = new DefaultResourceLoader();
YamlPropertySourceLoader ymlLoader = new YamlPropertySourceLoader();
try {
List<PropertySource<?>> sourceList = ymlLoader
.load(ymlFilePath, loader.getResource(ymlFilePath));
for (PropertySource<?> propertySource : sourceList) {
environment.getPropertySources().addLast(propertySource);
}
} catch (IOException e) {
e.printStackTrace();
}
}
上面的代码中,主要实作了:
- 获取当前环境
Environment
,当ApplicationEnvironmentPreparedEvent
事件被触发时,已经完成了Environment
的装载,并且能够通过event
事件获取 - 通过
YamlPropertySourceLoader
加载、决议组态档 - 将决议完成后的
OriginTrackedMapPropertySource
添加到Environment
中
修改启动类,在启动类中加入这个监听器:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
application.addListeners(new YmlListener("classpath:/application2.yml"));
application.run(args);
}
在向environment
中添加propertySource
前加一个断点,查看环境的变化:
执行完成后,可以看到组态档源已经被添加到了环境中:
启动完成后再呼叫一下界面,查看结果:
susan
female
能够正确的取到组态档中的值,说明自定义的监听器已经生效,
4、SnakeYml
前面介绍的几种方式,在Spring环境下无需引入其他依赖就可以完成的,接下来要介绍的SnakeYml
在使用前需要引入依赖,但是同时也可以脱离Spring环境单独使用,先引入依赖坐标:
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.23</version>
</dependency>
准备一个yml组态档:
person1:
name: hydra
gender: male
person2:
name: susan
gender: female
在使用SnakeYml
决议yml时,最常使用的就是load
、loadlAll
、loadAs
方法,这三个方法可以加载yml档案或字符串,最后回传决议后的物件,我们先从基础的load
方法开始演示:
public void test1(){
Yaml yaml=new Yaml();
Map<String, Object> map =
yaml.load(getClass().getClassLoader()
.getResourceAsStream("snake1.yml"));
System.out.println(map);
}
运行上面的代码,打印Map中的内容:
{person1={name=hydra, gender=male}, person2={name=susan, gender=female}}
接下来看一下loadAll
方法,它可以用来加载yml中使用---
连接符连接的多个档案,将上面的yml档案进行修改:
person1:
name: hydra
gender: male
---
person2:
name: susan
gender: female
在添加了连接符后,尝试再使用load
方法进行决议,报错如下显示发现了另一段yml档案从而无法正常决议:
这时候修改上面的代码,使用loadAll
方法:
public void test2(){
Yaml yaml=new Yaml();
Iterable<Object> objects =
yaml.loadAll(getClass().getClassLoader()
.getResourceAsStream("snake2.yml"));
for (Object object : objects) {
System.out.println(object);
}
}
执行结果如下:
{person1={name=hydra, gender=male}}
{person2={name=susan, gender=female}}
可以看到,loadAll
方法回传的是一个物件的迭代,里面的每个物件对应yml中的一段档案,修改后的yml档案就被决议成了两个独立的Map,
接下来再来看一下loadAs
方法,它可以在yml决议程序中指定型别,直接封装成一个物件,我们直接复用上面的snake1.yml
,在决议前先创建两个物体类物件用于接收:
@Data
public class Person {
SinglePerson person1;
SinglePerson person2;
}
@Data
public class SinglePerson {
String name;
String gender;
}
下面使用loadAs
方法加载yml,注意方法的第二个自变量,就是用于封装yml的物体型别,
public void test3(){
Yaml yaml=new Yaml();
Person person =
yaml.loadAs(getClass().getClassLoader().
getResourceAsStream("snake1.yml"), Person.class);
System.out.println(person.toString());
}
查看执行结果:
Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))
实际上,如果想要将yml封装成物体物件,也可以使用另一种方法,在创建Yaml
物件的时候,传入一个指定物体类的构造器物件,然后直接呼叫load
方法就可以实作:
public void test4(){
Yaml yaml=new Yaml(new Constructor(Person.class));
Person person = yaml.load(getClass().getClassLoader().
getResourceAsStream("snake1.yml"));
System.out.println(person.toString());
}
执行结果与上面相同:
Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))
SnakeYml其实实作了非常多的功能,这里就不一一列举了,有兴趣的小伙伴可以自己查看一下档案,如果你看了上一篇的文章后跟着翻阅了一下原始码,那么你会发现,其实在SpringBoot的底层,也是借助了SnakeYml来进行的yml的决议操作,
5、jackson-dataformat-yaml
相比大家平常用jackson比较多的场景是用它来处理json,其实它也可以用来处理yml,使用前需要引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.12.3</version>
</dependency>
使用jackson读取yml也非常简单,这里用到了常用的ObjectMapper
,在创建ObjectMapper
物件时指定使用YAML
工厂,之后就可以简单的将yml映射到物体:
public void read() throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
InputStream input =
new FileInputStream("F:\\Work\\yml\\src\\main\\resources\\snake1.yml");
Person person = objectMapper.readValue(input, Person.class);
System.out.println(person.toString());
}
运行结果:
Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=susan, gender=female))
如果想要生成yml档案的话,可以呼叫ObjectMapper
的writeValue
方法实作:
public void write() throws IOException {
Map<String,Object> map=new HashMap<>();
SinglePerson person1 = new SinglePerson("Trunks", "male");
SinglePerson person2 = new SinglePerson("Goten", "male");
Person person=new Person(person1,person2);
map.put("person",person);
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
objectMapper
.writeValue(new File("F:\\Work\\yml\\src\\main\\resources\\jackson-gen.yml"),map);
}
查看生成的yml档案,可以看到jackson对字符串型别严格的添加了引号,还在档案的开头添加了yml的链接符,至于其他jackson读写yml的复杂功能,大家可以在作业中自己去探索使用,
总结
本文介绍了5种读取yml组态档的方式,前3种依赖于Spring环境,而SnakeYml
和Jackson
则可以脱离环境独立使用,可以说它们是对@Value
和@ConfigurationProperties
注解使用的补充,这几种方法的使用场景不同,也各有各的优点,各自具备一些特殊的用法,而我们在作业中更多情况下,要根据具体的用途进行一种方案的选取或多种的搭配使用,
好了,希望这篇实战能够帮助到大家,我是Hydra,我们下篇再见,
作者简介,码农参上,一个热爱分享的公众号,有趣、深入、直接,与你聊聊技术,个人微信DrHydra9,欢迎添加好友,进一步交流,
0 评论