[02] SpringBoot的项目属性配置
1、application.properties 简述
配置文件的使用和调整都非常方便,直接在项目默认的classpath下的application.properties文件中做调整即可。例如SpringBoot中默认的Tomcat毒启动端口是8080,我们需要调整为8081,那么直接在配置文件中调整server.port属性即可:
默认的application配置文件是 .properties 格式,实际上你还可以使用 YAML 格式,即 .yml,如上图中的配置用 YAML 格式写则是:
YAML 格式的配置文件,以缩进来表示属性之间的层级关系,从上面两种的对比我们已经能看出,YAML 格式的配置文件结构更加简洁清晰。当然,如果使用 YAML 格式的配置文件的话,还需要额外学习一下它的语法(阮一峰:YAML 语言教程),主要是结构表示,特别在书写时还应注意 缩进/空格 的使用,保证语法的正确性。
2、配置文件的加载顺序
(官方说明: 24.3 Application Property Files)
SpringBoot应用会从如下四个位置加载application.properties文件,读取并载入到应用环境中去:
- 当前目录下的 /config 目录
- 当前目录
- classpath下的 /config 目录
- classpath 根目录
注意:如上的顺序是按照优先级排列的(从高到低),这意味着哪怕是同一个key的配置分别在不同位置的文件中,会优先选择 "当前目录下的 /config 目录"下的配置文件的值,也就是说,如果在优先级更高的位置找到了配置,则会无视优先级低的配置。这同样也意味着如果你想在测试/生产环境中快速修改配置参数,只需要在jar包之外的配置文件进行修改即可,不需要重新打包和部署应用。
这里的当前目录,是指与SpringBoot项目jar包同级的目录,如某个项目打包后的jar包在文件夹 /demo/springboot-demo.jar,则对应如上为:
- /demo/config/application.properties
- /demo/application.properties
- /demo/springboot-demo.jar/BOOT-INF/classes/config/application.properties
- /demo/springboot-demo.jar/BOOT-INF/classes/application.properties
当然,实际上展开来说SpringBoot读取配置的优先级不止上面的这几种,比如说优先级最高的实际上是命令行参数,即你在启动 jar 时通过命令行传入的配置参数。
3、自定义属性与加载
在类中,我们可以通过注解 @Value("${属性名}") 的方式将配置文件中的值注入到类中,如下配置文件有值:
server:
port: 8081
person:
name: zhangsan
age: 18
6
1
server
2
port8081
3
4
person
5
name zhangsan
6
age18
接着我们可以使用注解 @Value("${person.xxx}") 来将配置文件的值读入:
@Component
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
31
1
2
public class Person {
3
"${person.name}") (
4
private String name;
5
"${person.age}") (
6
private int age;
7
8
public String getName() {
9
return name;
10
}
11
12
public void setName(String name) {
13
this.name = name;
14
}
15
16
public int getAge() {
17
return age;
18
}
19
20
public void setAge(int age) {
21
this.age = age;
22
}
23
24
25
public String toString() {
26
return "Person{" +
27
"name='" + name + '\'' +
28
", age=" + age +
29
'}';
30
}
31
}
写个Controller看看吧:
4、@ConfigurationProperties
自定义属性与加载中提到的 @Value 方法,在配置较多时仍然显得有些繁重,SpringBoot提供了更简洁的方式,即 @ConfigurationProperties,该注解还有一个属性 prefix,可以指定配置文件的前缀,并且对类按属性名进行自动匹配。如上例中的 Person,换用 @ConfigurationProperties 的话,则如下:
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
30
1
2
prefix = "person") (
3
public class Person {
4
private String name;
5
private int age;
6
7
public String getName() {
8
return name;
9
}
10
11
public void setName(String name) {
12
this.name = name;
13
}
14
15
public int getAge() {
16
return age;
17
}
18
19
public void setAge(int age) {
20
this.age = age;
21
}
22
23
24
public String toString() {
25
return "Person{" +
26
"name='" + name + '\'' +
27
", age=" + age +
28
'}';
29
}
30
}
注意添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
5
1
<dependency>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-configuration-processor</artifactId>
4
<optional>true</optional>
5
</dependency>
5、参数间的引用
在 application.properties 文件中,各个参数也可以相互引用,仍然是通过 ${属性名} 的方式,如下 zhangsan.name 就会引用 person.name 的值,age同理:
server:
port: 8081
person:
name: zhangsan
age: 18
zhangsan:
name: ${person.name}
age: ${person.age}
10
1
server
2
port8081
3
4
person
5
name zhangsan
6
age18
7
8
zhangsan
9
name $ person.name
10
age $ person.age
6、使用随机值
在某些情况下,我们希望某些配置不是一个固定的值,那么可以在配置文件中使用 ${random.xx} 来产生int、long、String等随机值。如:
# 随机字符串
person.value=${random.value}
person.name=${random.string}
# uuid
person.name=${random.uuid}
# 随机int
person.age=${random.int}
# 随机long
person.height=${random.long}
# 10以内的随机数
preson.age=${random.int(100)}
# 10-20的随机数
person.name=${random.int[10,20]}
18
1
# 随机字符串
2
person.value=${random.value}
3
person.name=${random.string}
4
5
# uuid
6
person.name=${random.uuid}
7
8
# 随机int
9
person.age=${random.int}
10
11
# 随机long
12
person.height=${random.long}
13
14
# 10以内的随机数
15
preson.age=${random.int(100)}
16
17
# 10-20的随机数
18
person.name=${random.int[10,20]}
7、Environment
这种方式是通过依赖注入Spring的Environment类实现的,并通过该类的getProperty方法配置的值:
@RestController
@RequestMapping("/demo/")
public class DemoController {
@Autowired
private Environment environment;
@RequestMapping("/printPerson.do")
public String printPerson() {
String personName = environment.getProperty("person.name");
return personName;
}
}
14
1
2
"/demo/") (
3
public class DemoController {
4
5
6
private Environment environment;
7
8
"/printPerson.do") (
9
public String printPerson() {
10
String personName = environment.getProperty("person.name");
11
return personName;
12
}
13
14
}
另外在测试时发现一个小坑值得注意,当配置文件中的 person.name 值采用了 ${random.value},并绑定到 Person 类中,你会发现 environment 再每次获取 person.name 的值时是随机的,但是Person类中通过person.name赋予的值只是第一次的值,之后将不会主动改变:
@RestController
@RequestMapping("/demo/")
public class DemoController {
@Autowired
private Person person;
@Autowired
private Environment environment;
@RequestMapping("/printPerson.do")
public String printPerson() {
String nameFromClass = person.getName();
String nameFromEnv = environment.getProperty("person.name");
System.out.println("class: " + nameFromClass);
System.out.println("env: " + nameFromEnv);
return nameFromEnv;
}
}
20
1
2
"/demo/") (
3
public class DemoController {
4
5
6
private Person person;
7
8
9
private Environment environment;
10
11
"/printPerson.do") (
12
public String printPerson() {
13
String nameFromClass = person.getName();
14
String nameFromEnv = environment.getProperty("person.name");
15
System.out.println("class: " + nameFromClass);
16
System.out.println("env: " + nameFromEnv);
17
return nameFromEnv;
18
}
19
20
}
分别访问三次,得到输出如下:
class: 80c628c7948c4270f831956945a0f26e
env: d8ff3f804268a929d365c663ade6894c
class: 80c628c7948c4270f831956945a0f26e
env: 9351315473098298f8416dae16bd1acb
class: 80c628c7948c4270f831956945a0f26e
env: ef78a4148b049c3fc25a71bddb560715
6
1
class: 80c628c7948c4270f831956945a0f26e
2
env: d8ff3f804268a929d365c663ade6894c
3
class: 80c628c7948c4270f831956945a0f26e
4
env: 9351315473098298f8416dae16bd1acb
5
class: 80c628c7948c4270f831956945a0f26e
6
env: ef78a4148b049c3fc25a71bddb560715
8、多环境配置 application-{profile}
我们在实际开发时,开发环境配置、测试环境配置、正式生产环境配置,配置信息都是不一样的(如开发环境的Tomcat端口是8080,生产环境的Tomcat端口则是8089),在频繁修改配置的过程中很容易出错。SpringBoot提供了更加简单的方式来解决这种问题,即通过配置多份不同环境下的配置文件。如原来仅有 application.yml,现在则新增三项,分别命名为:
- application-dev.yml
- application-test.yml
- application-prod.yml
而在主配置文件 application.yml 中,则通过属性 spring.profiles.active 来指定使用哪份配置文件,如在开发时则指定 spring.profiles.active=dev,生产环境时 spring.profiles.active=prod
也可以在命令行启动时进行配置,这样哪怕重新部署也不需要修改application.yml:
java -jar xxx.jar --spring.profiles.active=prod
1
1
java -jar xxx.jar --spring.profiles.active=prod
9、加载多个自定义配置文件
我们当然喜欢多项配置能独立在不同的配置文件中进行管理,隔离而清晰,这就要求SpringBoot能够加载多个配置文件。如我们在 src/main/resource 下新建目录 config,新建配置文件 person.properties:
那么我们需要在Person类的注解上新增两个注解:@Configuration 和 @PropertySource,其中用 @PropertySource 注解中的 value 属性来指定需要读取的配置文件的路径,如下:
@Component
@Configuration
@PropertySource("classpath:config/person.properties")
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
32
1
2
3
"classpath:config/person.properties") (
4
prefix = "person") (
5
public class Person {
6
private String name;
7
private int age;
8
9
public String getName() {
10
return name;
11
}
12
13
public void setName(String name) {
14
this.name = name;
15
}
16
17
public int getAge() {
18
return age;
19
}
20
21
public void setAge(int age) {
22
this.age = age;
23
}
24
25
26
public String toString() {
27
return "Person{" +
28
"name='" + name + '\'' +
29
", age=" + age +
30
'}';
31
}
32
}
这里不再使用 person.yml 作为配置文件,是因为 @PropertySource 注解不支持YAML文件格式,官方说明 “YAML files cannot be loaded by using the @PropertySource annotation. So, in the case that you need to load values that way, you need to use a properties file.” 看来在SpringBoot中,作为配置文件,还是使用 .properties 更为兼容。
10、打包jar时需要注意的自定义配置文件打包
在IDE上运行时正常,在打包项目为jar时再运行就提醒找不到配置文件,而这些配置文件往往是我们自定义的。我们到打包后的jar中目录下 BOOT-INF/classes 中发现,自定义的配置文件没有打包进来,这时需要注意,在pom.xml中添加 <resource>:
<build>
<!-- 打包资源文件 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
11
1
<build>
2
<!-- 打包资源文件 -->
3
<resources>
4
<resource>
5
<directory>src/main/resources</directory>
6
<includes>
7
<include>**/*</include>
8
</includes>
9
</resource>
10
</resources>
11
</build>
其中 <include> 中的 **/* 表示通配符,所有目录下的所有文件,你也可以 **/*.properties 诸如此类。
11、@Configuration 和 @Bean
这个具体来说更应该是bean的注解配置方式:
- @Configuration 标注在类上,相当于spring的xml配置文件中的 <beans>,作用为配置spring容器(应用上下文)
那么相应的
- @Bean 标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的 <bean>,作用为注册bean对象