SpringBoot2 | 第二篇:SpringBoot配置详解

第一篇介绍了 SpringBoot 由来及构建方式,通过第一章的教程我们对 SpringBoot 不再感到陌生,可以发现 SpringBoot 虽然干掉了 XML 但未做到 零配置,它体现出了一种 ==约定优于配置,也称作按约定编程==,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,这种方式简单又不失灵活。 一般情况下默认的配置足够满足日常开发所需,但在特殊的情况下,我们往往需要用到 自定义属性配置、自定义文件(配置文件)配置、多环境配置、外部命令引导 等一系列功能。不用担心,这些 SpringBoot 都替我们考虑好了,我们只需要遵循它的规则配置即可

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.0.5.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9

文件处理器的使用

为了让 Spring Boot 更好的生成配置元数据文件,我们需要添加如下依赖(该依赖可以不添加,但是在 IDEA 和 STS 中不会有属性提示,没有提示的配置就跟你用记事本写代码一样苦逼,出个问题弄哭你去),该依赖只会在编译时调用,所以不用担心会对生产造成影响…

1
2
3
4
5
6
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <!-- true表示两个项目之间依赖不传递;不写默认为false -->
</dependency>

自定义属性值通过@ConfigurationProperties的prefix值绑定,自定义属性便可与类中的属性一样映射,如果导入配置文件处理器,那么在配置文件中就可以有提示了。

360截图175711169894143

效果

360截图17001020447867

注意:如果没有提示则重新 编译 一下即可

360截图17001020447867

经测试,该处理器只对 application.ymlapplication.properties 有效,对如 test.properties 无效,所以当我们需要使用的时候,可以先用 application.properties 命名配置文件,写完改为 xxx.properties 即可。

(这方法看一下,可以在后面测试中使用,挺方便的)

1、配置文件

SpringBoot 使用一个 全局 的配置文件,配置文件名是 固定 的;该配置文件可以对一些默认配置进行修改

两种写法

  • application.properties
  • application.yml

​ 析: ymlYAML (YAML Ain’t Markup Language)语言的文件,以数据为中心,比 jsonxml 等更 适合 做配置文件。语法规范可参考 http://yaml.org/

示例

  • application.properties

    1
    server.port: 8081
  • application.yml

    1
    2
    server:
    port: 8081

2、YAML

2.1、基本语法

k:(空格)v:表示一对键值对(空格必须有);

它是以 空格 的缩进来控制 层级 关系;只要是左对齐的一列数据,都是同一个层级的

1
2
3
server:
port: 8081
path: /hello

:属性和值也是大小写敏感;

2.2、值的写法

2.2.1、字面量

普通的值(数字,字符串,布尔)

k: v:字面直接来写;

​ 字符串 默认不用加单引号 或者 双引号;加了的话有特殊意义,如下

“”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思

​ name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi

‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据

​ name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi

2.2.2、对象、Map

k: v:写对象或集合的属性和值的关系;注意缩进

普通写法

1
2
3
friends:
lastName: zhangsan
age: 20

行内写法

1
friends: {lastName: zhangsan, age: 18}

2.2.3、数组、List、Set

-值表示数组中的一个元素

普通写法

1
2
3
4
5
# 用`-`值表示数组中的一个元素,后面有一个空格
pets:
- cat
- dog
- pig

行内写法

1
pets: [cat,dog,pig]

3、配置文件值注入

3.1、全局配置文件值注入

全局配置文件: application.propertiesapplication.yml ,这里以 application.yml 为例

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#自定义属性值
fatal:
name: fatal
age: 20
fatal6: {name: brother, age: 21} # 测对象 行内写法
map: {one: 1, two: 2, three: 3} # 测 Map 集合 行内写法
strings: [cat,dog,pig,strings] # 测数组 行内写法
list: [cat,dog,pig,list] # 测 List 集合 行内写法
set: [cat,dog,pig,set] # 测 Set 集合 行内写法
# 数组或集合的普通写法
# strings:
# - cat
# - dog
# - pig

Component(@Value)

其他组件如:@Controller、@Service 也可以和 @Value 一起用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.fatal.config;

import lombok.Data;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* 配置类
* @author: Fatal
* @date: 2018/9/20 0020 16:16
*/
@Component
@ToString
@Data
public class Fatal1 {

@Value("${fatal.name}")
private String name;

@Value("${fatal.age}")
private Integer age;

}

Component(@ConfigurationProperties)

一定要加上注解 @Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.fatal.config;

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* 配置类
* @ConfigurationProperties: 用于配置文件与配置类数据映射
* prefix: 与配置文件中`prefix`值下的所有属性进行一一映射
* @author: Fatal
* @date: 2018/9/20 0020 16:16
*/
@Component
@ConfigurationProperties(prefix = "fatal")
@ToString
@Data
public class Fatal2 {

private String name;

private Integer age;

/**
* 测试对象、Map
*/
private Fatal6 fatal6;

private Map<String, Integer> map;

/**
* 测试集合(List,Set)、数组数据封装
*/
private String[] strings;

private List<String> list;

private Set<String> set;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.fatal.config;

import lombok.Data;
import lombok.ToString;

/**
* 配置类
* @author: Fatal
* @date: 2018/9/20 0020 16:16
*/
@ToString
@Data
public class Fatal6 {

private String name;

private Integer age;

}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.fatal.controller;

import com.fatal.config.Fatal2;
import com.fatal.config.Fatal1;
import com.fatal.config.Fatal3;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
* @author: Fatal
* @date: 2018/9/20 0020 16:21
*/
@RestController
public class FatalController {

@Autowired
private Fatal1 fatal1;

@Autowired
private Fatal2 fatal2;

@RequestMapping("/getPropertyValue1")
public Map<String, Object> showComponentProperty1() {
Map<String, Object> map = new HashMap<>();
map.put("注解","@Value");
map.put("name", fatal1.getName());
map.put("age", fatal1.getAge());
return map;
}

@RequestMapping("/getPropertyValue2")
public Map<String, Object> showComponentProperty2() {
Map<String, Object> map = new HashMap<>();
map.put("注解","@ConfigurationProperties");
map.put("name", fatal2.getName());
map.put("age", fatal2.getAge());
map.put("fatal6", fatal2.getFatal6()); // 对象
map.put("map", fatal2.getMap()); // Map
map.put("strings", fatal2.getStrings()); // 数组
map.put("list", fatal2.getList()); // List
map.put("set", fatal2.getList()); // Set
return map;
}

}

3.1.1、@Value获取值和@ConfigurationProperties获取值(方式)比较

@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定(松散语法) 支持 不支持
SpEL(spring表达式语言) 不支持 支持
JSR303数据校验 支持 不支持
复杂类型封装 支持 不支持

松散绑定(松散语法):比如实体类有个属性叫 lastName,那么在配置文件中 lastNamelast-name 两种写法都是正确的。

复杂类型封装:属性又是一个实体,多层嵌套(复用)

注意:不管配置文件是 yml 还是 properties 他们 能获取到值;

3.1.2、使用场景

  1. @Value

    我们只是在某个业务逻辑中需要获取配置文件中的某项值,使用@Value

  2. @ConfigurationProperties

    我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties

3.2、配置文件注入值数据校验

以全局为例,校验的话对自定义和全局都有效。

application.yml

1
2
3
4
5
#自定义属性值
fatal:
name: fatal
age: 20
email: 634136073@qq.com # 用于测试值注入校验

Component(@ConfigurationProperties+@Validated)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.fatal.config;

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import javax.validation.constraints.Min;

/**
* 配置类
* @author: Fatal
* @date: 2018/9/20 0020 16:16
*/
@Component
@ConfigurationProperties(prefix = "fatal")
@ToString
@Data
@Validated // 开启校验
public class Fatal3 {

private String name;

@Min(20)
private Integer age;

@Email
private String email;

}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
  @Autowired
private Fatal3 fatal3;

@RequestMapping("/getPropertyValue3")
public Map<String, Object> showComponentPropertyWithValidated() {
Map<String, Object> map = new HashMap<>();
map.put("注解","@ConfigurationProperties + @Validated");
map.put("name", fatal3.getName());
map.put("age", fatal3.getAge());
map.put("email", fatal3.getEmail());
return map;
}

分析:

在配置类上加上注解 @Validated ,然后在属性上加上 校验注解 (javax.validation.constraints.*)。如果校验失败,项目启动时就会报错,例如邮箱 格式不正确 的话,就会出现如下 报错

1
2
3
4
5
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'fatal' to com.fatal.config.Fatal3 failed:
Property: fatal.email
Value: 63
Origin: class path resource [application.yml]:5:10
Reason: 不是一个合法的电子邮件地址

3.3、自定义配置文件值注入

上面的配置文件值注入我们都是把配置数据放在 application.yml 中,但是一般情况下,我们都不希望把自定义数据放到 application.yml 中(这样数据较乱),这时候我们可以自定义配置文件,如:test.properties;

test.properties

1
2
fatal.name=fatal
fatal.age=20

Component(@PropertySource)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.fatal.config;

import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
* 配置类
* @author: Fatal
* @date: 2018/9/20 0020 16:16
*/
@Component
@ConfigurationProperties(prefix = "fatal")
@PropertySource(value = {"classpath:test.properties"})
@ToString
@Data
public class Fatal4 {

private String name;

private Integer age;

}

分析:

application.yml 或者 application.properties 来给配置类注入值,不需要我们指定配置文件的位置,SpringBoot 默认会加载;而当我们使用自定义配置文件的时候,我们需要告诉SpringBoot该配置类与哪个配置文件进行数据映射,这时候我们可以用注解 @PropertySource() 注解,并用其属性 value 指定一个(或多个)配置文件。(需要 注意 的是,classpath 与 文件路径中间不能有空格)

4、配置文件占位符

4.1、使用随机数

1
2
3
4
5
6
${random.value}
${random.int}
${random.long}
${random.uuid}
${random.int(10)}
${random.int[1024,65536]}

4.2、属性配置占位符

可以在配置文件中引用前面配置过的属性

${fatal.age:默认值} 来指定找不到属性时的默认值

1537448921353

1537449074485

5、多环境化配置(两种方式)

在真实的应用中,常常会有多个环境(如:开发,测试,生产等),不同的环境数据库连接都不一样,这个时候就需要用到spring.profile.active 的强大功能了,它的格式为 application-{profile}.yml/properties,这里的 application 为前缀不能改,{profile} 是我们自己定义的。

5.1、多个文件写

application-dev.properties

1
server.servlet.context-path=/dev

application-test.properties

1
server.servlet.context-path=/test

application-prod.properties

1
server.servlet.context-path=/prod

application.properties 配置文件中写入 spring.profiles.active=dev,这个时候我们再次访问 http://localhost:8080/getPropertyValue1 就没用处了,因为我们设置了它的context-path=/dev,所以新的路径就是 http://localhost:8080/dev/getPropertyValue1由此可以看出来我们激活不同的配置读取的属性值是不一样的

5.2、yml支持多文档块方式(同一文件写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
port: 8081
spring:
profiles:
active: prod #指定属于哪个环境

---
server:
port: 8083
spring:
profiles: dev


---
server:
port: 8084
spring:
profiles: prod

5.3、外部命令引导

前面两种方式都是基于配置文件层面的,那么有没有办法外部引导呢,假设这样的场景,我们对已经开发完成的代码打包发布,期间在测试环境测试通过了,那么即可发布上生产,这个时候是修改application.properties的配置方便还是直接在命令参数配置方便呢,毫无疑问是后者更有说服力。默认情况下,SpringApplication 会将命令行选项参数(即:––property,如––server.port=9000)添加到Environment,命令行属性始终优先于其他属性源。

如何测试?

  • yml 的多文档块方式为例

  • 进入到项目目录,此处以我本地目录为主:E:/java/IdeaProjects2p0-learni/springboot2ng/chapter2

  • 然后打开 cmd 程序,不会在当前目录打开 cmd 的请自行百度,输入:mvn package

  • 打包完毕后进入到:E:/java/IdeaProjects2p0-learni/springboot2ng/chapter2/target 目录中去,我们可以发现一个名为chapter2-0.0.1-SNAPSHOT.jar 的包

  • 接着在target 打开 cmd 程序,输入:java -jar chapter2-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev --fatal.age=32。仔细观察spring.profiles.active=dev 、fatal.age=32 这俩配置的键值是不是似曾相识,对,它表示用命令行自定义配置属性,它会覆盖掉配置文件中的属性值(不认识的请从开头认真阅读)

  • 最后输入测试地址:http://localhost:8083/getPropertyValue1 我们可以发现返回的 JSON 和下面相似就表示正确

360截图184307026069103

360截图17321123108108122

6、配置文件的加载位置

SpringBoot 启动会扫描以下位置的 application.propertiesapplication.yml 文件作为 SpringBoot默认 配置文件

1
2
3
4
-file:/config/      # 当前项目的根目录的config文件夹下
-file:/ # 当前项目的根目录下
-classpath:/config/ # 类路径的config文件夹下 resources/config/
-classpath:/ # 类路径下 resources/

==注意==:当项目为多模块项目的时候,file 指的是 父工程 的根目录下,放子工程根目录没用。

规则

  1. (上到下)优先级由高到底,相同的配置项,高优先级的配置会 覆盖 低优先级的配置;
  2. 如果出现 不同的配置项,会形成 互补配置 共同起作用;

另外

我们还可以通过 spring.config.additional-location(配置项) 来改变默认配置文件的加载位置

项目打包好以后,我们可以使用 命令行参数 的形式,启动项目的时候来指定配置文件的加载位置;新配置文件默认加载的这些配置文件 ==共同作用形成== 互补配置

测试

1. classpath:/config/ 覆盖 classpath:/

360截图177509199013083

2. file:/ 覆盖 classpath:/config/

360截图177509199013083

3. file:/config/ 覆盖 file:/

360截图177509199013083

4. 外部配置文件

D:\application.properties

命令行加参数 ==spring.config.additional-location==

1
java -jar chapter2-0.0.1-SNAPSHOT.jar --spring.config.additional-location=D:\application.properties

:如果使用 spring.config.location 的话,那么默认包下的全局配置文件无效

步骤
  1. 编辑 D:\application.properties

    1
    fatal.name=outward/properties
  2. 打包

    360截图177509199013083

  3. 运行

    360截图177509199013083

  4. 页面

    360截图177509199013083

补充:命令行参数

所有的配置都可以在命令行上进行指定

1
java -jar chapter2-0.0.1-SNAPSHOT.jar --server.port=9090

多个配置用空格分开; --配置项=值

7、配置加载顺序(覆盖、互补)

SpringBoot也可以从以下位置加载配置; 优先级从高到低;高优先级的配置==覆盖==低优先级的配置,所有的配置会形成==互补==配置

规则

在 spring.profiles.active指定一个环境后,属性的优先级如下:

由jar包外向jar包内进行寻找;

顺序为:包外的config下的优先级最高(在config文件中也是优先profile)

优先加载带profile

  1. jar 包外部 application-{profile}.propertiesapplication.yml(带spring.profile) 配置文件

  2. jar 包内部application-{profile}.propertiesapplication.yml(带spring.profile) 配置文件

再来加载不带profile

  1. jar 包外部application.propertiesapplication.yml(不带spring.profile) 配置文件

  2. jar 包内部application.propertiesapplication.yml(不带spring.profile) 配置文件

所有支持的配置加载来源:参考官方文档

测试

  1. 打包
  2. 创建两个配置文件,分别为 application.propertiesapplication-dev.properties
  3. 运行(java -jar chapter2-0.0.1-SNAPSHOT.jar)

360截图177509199013083

360截图177509199013083

360截图177509199013083

包内外的 application-dev.properties 如果配置项不同,则形成互补。

总结

  1. @ConfigurationProperties:用于 配置文件配置类 数据映射;

    其属性 prefix:与配置文件中prefix下的所有属性进行 一 一 映射;

  2. 在某个业务逻辑中需要获取配置文件中的 某项值,使用@Value;编写了一个javaBean来和配置文件进行映射,我们就直接使用 @ConfigurationProperties

  3. @PropertySource 加载指定配置文件;

  4. ControllerComponent 都是Spring 容器中的组件;

  5. 在项目根目录下,mvn package可以打包,在生成包所在目录下,java -jar chapter2-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev --fatal.age=32可以自定义参数运行包

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter2

学习 唐亚峰方志朋 前辈的经验