配置文件基本使用

配置文件基本使用

文件类型

properties

application.properties,这里就不多介绍了

yaml

YAML的官方文档,其中语法相关的部分:language-overview

YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),XML,也是一种标记语言。

非常适合用来做以数据为中心的配置文件,YAML 将来会成为配置文件主流的格式,一定要掌握

其实,在 Python 中,也有一种文件格式专门用于配置文件,那就是 TOML,具体请看《TOML 配置文件.md

基本语法

数据类型

字面量 - scalar

单个的、不可再分的值。date、boolean、string、number、null

k: v
对象

键值对的集合。map、hash、set、object

行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
  k1: v1
  k2: v2
  k3: v3
数组

一组按次序排列的值。array、list、queue

行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

实践

建立相关的 bean

@Data
@Component
@ConfigurationProperties(prefix = "student")
public class Student {

    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private String studentInfo;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;

}

此类型中使用到的 Pete

@Data
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
public class Pet {
    @NotNull
    private String name;

    private String breed;
}

yaml 中对应的配置

student:
#  默认,会将属性名从驼峰命名法(camelCased)转化为串式命名法(kebab-cased),举个例子 属性名为userName,在yaml中,通过user-name设置
  user-name: zhagnsan
  boss: true
#  时间默认用 / 分割
  birth: 1993/10/15 21:21:00
  age: 22
  #  student-info: 'line one \n line two'
  #  student-info: line one \n line two
  student-info: "line one \n line two"
  interests:
    - 抽烟
    - 喝酒
    - 烫头
#  行内写法
  animal: [阿黄, 小黑]
  salarys: [15, 45.12, 78979]
#  key 为中文的时候,要特别注意,不能直接写,要转义一下
#  score: {"[语文]":20, "[数学]":15,"english":30}
  score: {Chinese: 20, Math: 15, English: 30}
#  行内写法
#  pet: {name: yufei, breed: 边牧}
  pet:
    name: yufei
    breed: 边牧
  all-pets:
    "[猫]":
      - {name: 狸花猫, breed: 狸花猫}
      - {name: 橘猫, breed: 橘猫}
      - {name: 英短, breed: 英短}
      - {name: 波斯猫, breed: 波斯猫}
    '[狗]':
      - name: 金毛
        breed: 金毛
      - name: 中华田园犬
        breed: 中华田园犬
      - name: 博美
        breed: 博美
      - name: 泰迪
        breed: 泰迪

启动测试

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) throws JsonProcessingException {
        final ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        Student bean = context.getBean(Student.class);
        // 字符串化
        ObjectMapper Obj = new ObjectMapper();
        String jsonStr = Obj.writeValueAsString(bean);
        System.out.println(jsonStr);
    }

}

输出:

{"userName":"zhagnsan","boss":true,"birth":750691260000,"age":22,"studentInfo":"line one \n line two","pet":{"name":"yufei","breed":"边牧"},"interests":["抽烟","喝酒","烫头"],"animal":["阿黄","小黑"],"score":{"Chinese":20,"Math":15,"English":30},"salarys":[15.0,45.12,78979.0],"allPets":{"猫":[{"name":"狸花猫","breed":"狸花猫"},{"name":"橘猫","breed":"橘猫"},{"name":"英短","breed":"英短"},{"name":"波斯猫","breed":"波斯猫"}],"狗":[{"name":"金毛","breed":"金毛"},{"name":"中华田园犬","breed":"中华田园犬"},{"name":"博美","breed":"博美"},{"name":"泰迪","breed":"泰迪"}]}}

JSON 格式化之后,是这样的

{
  "userName": "zhagnsan",
  "boss": true,
  "birth": 750691260000,
  "age": 22,
  "studentInfo": "line one \n line two",
  "pet": {
    "name": "yufei",
    "breed": "边牧"
  },
  "interests": [
    "抽烟",
    "喝酒",
    "烫头"
  ],
  "animal": [
    "阿黄",
    "小黑"
  ],
  "score": {
    "Chinese": 20,
    "Math": 15,
    "English": 30
  },
  "salarys": [
    15.0,
    45.12,
    78979.0
  ],
  "allPets": {
    "猫": [
      {
        "name": "狸花猫",
        "breed": "狸花猫"
      },
      {
        "name": "橘猫",
        "breed": "橘猫"
      },
      {
        "name": "英短",
        "breed": "英短"
      },
      {
        "name": "波斯猫",
        "breed": "波斯猫"
      }
    ],
    "狗": [
      {
        "name": "金毛",
        "breed": "金毛"
      },
      {
        "name": "中华田园犬",
        "breed": "中华田园犬"
      },
      {
        "name": "博美",
        "breed": "博美"
      },
      {
        "name": "泰迪",
        "breed": "泰迪"
      }
    ]
  }
}
复杂类型 key

如何在映射中使用复杂的 key?使用问号 ?

官方文档:YAML Ain’t Markup Language (YAML™) revision 1.2.2 中的 2.2. Structures 小节说的很清楚

具体解释:What is a complex mapping key in YAML? - Stack Overflow

添加属性

private Map<List<String>, List<User>> userOrganization;
private Map<Pet, List<User>> petHome;

User 类型的 bean

@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class User {
    @NonNull
    private String name;
    @NonNull
    private Integer age;

    private Pet pet;

}

编写相应的 yaml

user-organization:
  #    虽然提示波浪线,但是实际上没有报错
  #    没有成功
  ? - xiashuo
    - dfasda
  : - name: 'xiashuo'
      age: 21
pet-home:
#    虽然提示波浪线,但是实际上没有报错
#    没有成功
  ? name: xiashuo
    breed: 边牧
  : - name: xiashuo
      age: 21
    - name: dfasdfa
      age: 45

在编辑器中,虽然提示波浪线,但是实际上没有报错,IDEA 不支持 ? 的解析吗?

最终对应的这两个属性的解析结果是这样的

感觉 key 的解析缺了一步,此场景不常见,就留到以后再探索吧。TODO

此外,对应的 value 倒是都解析成功了

当 key 为字符串类型的列表的时候,有另一种实现方式

user-organization:
  "[xiashuo, dfasda]":
    - name: 'xiashuo'
      age: 21

对应的解析结果

注意点总结

下面重点说 ''"" 在转义特殊字符上的区别

使用 "" 会转义特殊字符,比如 \n

student-info: "line one \n line two"

在浏览 bean 的属性内容的时候,会换行

但是用 '' 或者压根儿不使用引号的时候

student-info: 'line one \n line two'

或者

student-info: line one \n line two

都不会转义

不常用的语法

---

YAML 使用三个破折号 (---) 将指令与文档内容分开。如果没有指示,这也可以用来指示文档的开始。三个点 (...) 在不开始新文档的情况下指示文档的结束,用于通信通道中。

& 和 *

重复的节点 (对象) 首先由一个锚 (用 & 号标记) 标识,然后进行别名 (用星号 * 引用)。举个例子

---
hr:
- Mark McGwire
# 给 Sammy Sosa 起了个别名 SS
- &SS Sammy Sosa
rbi:
# 在这引用SS ,即 Sammy Sosa
- *SS 
- Ken Griffey
| 和 >

感觉这个可以用于保存那种比较长,长到会换行的那种文本,比如项目的系统的介绍信息。由此可见,yaml 的能力确实是强,用来做配置文件很合适。

标量 (scalar) 内容可以用块表示法编写,使用文字样式 (由 | 表示) 时,其中所有换行符都是有效的。或者,也可以使用折叠样式 (由 > 表示) 编写换行符,其中每个换行符都被折叠为一个空格,除非换行符以空行或更缩进的行结束。

info0: |
  aaaa
  bbbb
  cccc
info1: >
  aaaa
  bbbb
  cccc
info2: > 
  aaaa
  bbbb
    cccc
    dddd
  eeee 

info0,所有的换行符都有效,映射到属性中,就是

info1 中,所有的换行符都折叠成了空格

info2 中,虽然还是使用 >,但是我们发现,只要缩进跟最外层的缩进不一样,就会换行,其余的行依然是换行折叠成空格

yaml 和 yaml

后缀为 yaml 和 yaml,有啥区别,

答案是没啥区别,使用 Yaml 和 yml 文件扩展名创建的 Yaml 文件,两者在解释和语法上是相同的。那为什么要做区分?因为在旧版本的 windows 中,扩展名限制为 3 个字母,如 yml。现在,没有操作系统级别的强制要求扩展名中有 3 个字母。大多数 yaml 用户使用 .yaml 作为首选。

yaml 相对于 properties 的优势

很明显,yaml 可以根据缩进判断出层级关系,就像代码格式化以后更容易看清楚层次一样,而 properties 就无法做到这一点。

以后写项目的配置信息,优先 yaml

跟 properties 一样

yaml 中配置的值,可以直接通过 @Value("${propertiesName}") 注解注入,但是注意,propertiesName 的值只能是文本,不能是 map 或者集合

properties 到 yaml 的转化插件:Convert YAML and Properties File - IntelliJ IDEs Plugin | Marketplace

配置提示

我们在 yaml 中编写 SpringBoot 自带的配置的时候,配置项都是有提示的,但是编写我们自己添加的配置(@ConfigurationProperties 注解的类的字段)的的时候,是没有自动提示的,我们希望也能够获得提示。

其实这个时候我们可以注意到,只要是使用了 @ConfigurationProperties 注解的类,IDEA 都会给这样一个提示:


实际上我们根据这个提示配置好 Configuration Annotation Processor(配置注解处理器),编写 yaml 的时候就会有提示了。

在《SpringBoot 基础篇 -3- 容器功能》中学习 @ConfigurationProperties 注解的时候,在 @ConfigurationProperties 注解的官网文档中,在官方文档(Configuration Metadata)里出现过引入这个依赖的内容

具体实现原理,在 《Lombok 常用注解.md》的 Annotation Processing 小节分析过。

根据文档内容提示,操作很简单,在 POM 中引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

在打包 build 的时候排除此依赖,因为这个注解处理只在开发的时候编写 yaml 有用,项目已经打包了就没啥用了,为了减小打包的体积,就排除这个依赖算了

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

注意:在 Spring Initailizr 中也可以选择此依赖,就不要手动添加了,记得添加

不仅是配置项的提示,按住 Ctrl 再鼠标左键点击配置项,可以跳转到此配置项对应的 @ConfigurationProperties 注解的类,这对我们进行源码分析的时候非常方便。

如果引入了 spring-boot-configuration-processor 之后,依然无法自动提示,则需要检查一下 IDEA 的设置,有的时候,下载别的插件或者更改系统设置,会去掉其中的一个文件拓展名,被去掉的文件拓展名将无法进行提示。

而且,配置文件还必须被配置为当前 SpringBoot 应用的配置文件,此时,配置文件的文件图标是:

像这种

就表示此配置文件还没有配置为当前 SpringBoot 应用的配置文件。

配置文件还没有配置为当前 SpringBoot 应用的配置文件的原因可能是因为此配置文件不在 classpath: 下的默认的几个配置文件位置中。添加到其中即可。

总结

yaml 的解析有没有配置文件可以定制一下,TODO?

时间久了,我们可以总结出自己的一套模板 yaml 配置

结合 SpringBoot 自动配置来看 SpringBoot 的配置文件,我们就能明白,SpringBoot 配置文件的意义在于将自动配置类的一部分参数外置(同时这些参数有默认值),在配置文件中自定义这些参数,就可以定制自动配置类的工作。但是自动配置类并没有把自动配置过程中所有的参数外置,只外置了一些常用的,所有并不是所有的配置都可以用配置文件配置,当想配置的内容自动配置类没有外置的时候,那要么修改自动配置类,要么只能手动进行 Java 配置。

自动配置相关知识,请看《SpringBoot 基础篇 -2- 自动配置.md