SpringBoot允许使用配置文件对应用程序进行配置,支持以下不同形式的配置源:
- 属性文件(比如application.properties)
- yaml文件(后缀可以是yml或者yaml)
- 环境变量
- 命令行参数
获取这些外部化属性有以下几种方式:
- 使用@Value注解
- 使用Spring的Environment抽象层进行访问
- 使用@ConfigurationProperties注解将一批特定属性绑定到java对象
配置源的加载顺序和优先级
由于先加载的配资源都有可能被后加载的配资源覆盖,因此认为后加载的配置源优先级更高,加载顺序如下:
- 默认属性(通过SpringApplication.setDefaultProperties()方法指定)
- 配置类(@Configuration修饰的类)上用@PropertySource注解加载的属性文件中的属性值
- 配置数据(如application.properties文件等)
- RandomValuePropertySource,只包含random.*中的属性
- 操作系统环境变量
- java系统属性(System的getProperties()方法返回的属性)
- 来自java:comp/env的jndi属性(只在Web服务器或应用服务器中有用)
- ServletContext的初始化参数(在Web.xml文件中通过<context-param.../>元素设置的初始化参数)(只在Web服务器或应用服务器中有用)
- ServletConfig的初始化参数(在Web.xml文件中通过<init-param.../>元素设置的初始化参数,或者通过@Servlet注解设置的初始化参数)
- 来自SPRING_APPLICATION_JSON的属性(嵌套在环境变量或系统属性中的JSON文本)
- 命令行参数
- 测试用例类上通过@SpringBootTest注解的properties所指定的属性(仅在单元测试生效)
- 测试用例类上用@TestPropertySource注解加载的属性文件中的属性值(仅在单元测试生效)
- 当SpringBoot的devtools工具处于激活状态时,用户Home目录中.config/spring-boot/子目录下spring-boot-devtools.properties或spring-boot-devtools.yml文件中设置的属性值(windows平台对应"C:\User\用户名"下的配置文件)
此外application.properties(包括application.yml)也有几个不同的来源,它们按如下顺序加载:
- jar包内的application.properties(或application.yml)
- jar包内的application-{profile}.properties(或application-{profile}.yml)
- jar包外临时指定的application.properties(或application.yml)
- jar包外临时指定的application-{profile}.properties(或application-{profile}.yml)
jar包外临时指定的配置文件优先级高于jar包内;特定的profile对应的配置文件优先级高于通用配置文件的优先级。
利用JSON参数配置
SpringBoot支持一个特殊的系统属性(环境变量):Spring.application.json,通过这个属性可传入一段json文本,而这段文本会被解析成配置属性来加载,加载顺序排在第10位(上一小节)。
java
@SpringBootApplication
public class App
{
public static void main(String[] args)
{
// 设置spring.application.json系统属性
// 属性值为{"fkjava":{name:""SPringBoot服务器", age:20, servers:["fkjava.org", "crazyit.org"]}}
System.setProperty("spring.application.json",
"{\"fkjava\":{\"name\":\"SPringBoot服务器\", \"age\":20, " +
"\"servers\":[\"fkjava.org\", \"crazyit.org\"]}}");
SpringApplication.run(App.class, args);
}
}
application.properties文件内容:
java
fkjava.name=springboot软件服务器
fkjava.age=35
java
@RestController
public class HelloController {
// 使用@Value注解访问配置属性
@Value("${fkjava.name}")
private String name;
@Value("${fkjava.age}")
private String age;
@Value("${fkjava.servers[0]}")
private String server1;
@Value("${fkjava.servers[1]}")
private String server2;
@GetMapping
public Map<String, String> hello()
{
return Map.of("名称", name, "年龄", age,
"服务器1", server1, "服务器2", server2);
}
}
输出结果:
可见,通过spring.application.json设置的属性覆盖了application.properties文件配置的属性。
使用YAML配置文件
application.properties文件的另一种形式是application.yml,这两种文件形式不同,本质一样。
YAML是JSON格式的超集,以层次格式来存储配置属性,SpringBoot使用SnakeYAML来解析YAML文件,由于spring-boot-starter自动依赖SnakeYAML,因此只要项目添加了spring-boot-starter依赖,SpringApplication就能解析YAML文件。
application.properties文件中的点号(.)对应yaml文件中的缩进,比如spring.application,name=cruncher对应于
java
spring:
application:
name: "cruncher"
如果YAML配置的属性包含多个列表项,那么它会被转化成【index】形式,比如
fkjava:
name: "SpringBoot软件"
age: 20
servers:
- www.fkjava.org
- www.crazyit.org
被转换为:
fkjava.servers[0]=www.fkjava.org
fkjava.servers[1]=www.crazyit.org
如果程序要加载YAML文件配置的属性,可使用以下工具类
- YamlPropertiesFactoryBean,它将YAML文件加载为Properties对象
- YamlMapFactoryBean,它将YAML文件加载为Map对象
- YamlPropertySourceLoader,它将YAML文件加载为PropertySource.
@PropertySource和@TestPropertySource都只能加载属性文件,不能加载Yaml文件,这是Yaml文件的局限之一。
文件路径:/resources/application.yml(系统会自动加载)
java
fkjava:
server:
name: "疯狂软件服务器"
port: 9000
文件路径:/resources/fk/fk.yml(系统不会自动加载)
java
fkjava:
name: "疯狂软件"
age: 20
servers:
- www.fkjava.org
- www.crazyit.org
java
//定义一个配置环境后处理器来加载fk.yml
public class FkEnvironmentPostProcessor implements EnvironmentPostProcessor
{
// 创建YamlPropertySourceLoader,用于加载YAML文件
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application)
{
// 指定自定义的配置文件
Resource path = new ClassPathResource("fk/fk.yml");
// 加载自定义配置文件
PropertySource<?> ps = null;
try
{
ps = this.loader.load("custom-resource", path).get(0);
}
catch (IOException e)
{
e.printStackTrace();
}
System.out.println("fkjava.name: " + ps.getProperty("fkjava.name"));
System.out.println("fkjava.age: " +ps.getProperty("fkjava.age"));
System.out.println("fkjava.servers[0]: " +ps.getProperty("fkjava.servers[0]"));
System.out.println("fkjava.servers[1]: " +ps.getProperty("fkjava.servers[1]"));
// 将PropertySource中的属性添加到Environment配置环境中
environment.getPropertySources().addLast(ps);
}
}
可通过/META-INF/spring.factories来注册配置环境后处理器:
java
org.springframework.boot.env.EnvironmentPostProcessor=org.crazyit.app.FkEnvironmentPostProcessor
这样一来application.yml和fk.yml都被加载进来,下来可以在其他任何Bean中通过@Value来访问它们
java
@RestController
public class HelloController
{
// 使用@Value注解访问配置属性
@Value("${fkjava.server.name}")
private String serverName;
@Value("${fkjava.server.port}")
private String serverPort;
@Value("${fkjava.age}")
private String age;
@GetMapping
public String hello()
{
return "名称: " + serverName + ", 端口: " + serverPort
+ ", 年龄: " + age;
}
}
如果仅需要加载自定义的Yaml文件,在组件中使用这些配置属性,并不需要将属性添加到配置环境中,那么只要在容器中配置一个YamlPropertiesFactoryBean工厂Bean或YamlMapFactoryBean工厂Bean,它们就会自动读取Yaml文件,将其中的配置内容加载为Properties对象或Map对象。
java
@Configuration
public class MyConfig
{
// 在容器中配置一个YamlPropertiesFactoryBean
@Bean
public YamlPropertiesFactoryBean fkProps()
{
var factory = new YamlPropertiesFactoryBean();
factory.setResources(new ClassPathResource("fk/fk.yml"));
return factory;
}
}
spring中的工厂Bean有一个特征:当程序通过spring容器获取工厂Bean时,实际获取到的是该工厂的产品(getObject()方法的返回值),因此当程序获取上面配置的fkProps时,实际返回的是一个Properties对象。
java
@RestController
public class HelloController
{
// 使用@Value注解访问配置属性
@Value("${fkjava.server.name}")
private String serverName;
@Value("${fkjava.server.port}")
private String serverPort;
// 指定将容器中fkProps Bean注入fkProps实例变量
@Resource(name = "fkProps")
private Properties fkProps;
@GetMapping
public String hello()
{
return "名称: " + serverName + ", 端口: " + serverPort
+ ", 年龄: " + fkProps.getProperty("fkjava.age");
}
}
改变配置文件的位置
默认情况下,SpringBoot或按如下顺序加载配置文件,后加载的文件会覆盖先加载的文件,因此,后加载的文件有更高的优先级
- 类加载路径的根路径
- 类加载路径下的/config子目录
- 当前路径
- 当前路径的/config子目录
- 当前路径的/config子目录的任意直接子目录,比如/config/abc、/config/aef等
如果想改变这种默认行为,可通过如下系统属性(或环境变量、或命令行参数)来设置
- spring.config.name:改变配置文件的文件名,默认是application。如果用OS环境变量来设置,则属性对应于SPRING_APPLICATION_NAME环境变量名。
- spring.config.location:改变配置文件的加载路径。如果用OS环境变量来设置,则属性对应于SPRING_APPLICATION_LOCATION环境变量名。
- spring.config.additional.location:添加配置文件的加载路径,不会覆盖原来的配置文件的加载路径。
不要使用SpringBoot配置属性来设置以上三种属性,因为这几个属性的加载实际非常早,只能通过系统环境变量、系统属性或命令行参数来设置。
通过spring.config.location或spring.config.additional.location指定的加载路径是有先后顺序的,比如spring.config.location设置为"optional:classpath:/fkjava/,optional:file:./fkit/",那么加载顺序为:
- optional:classpath:/fkjava/
- optional:file:./fkit/
如果使用spring.config.additional.location来添加配置文件的加载路径,那么新增路径总是排在默认的加载路径之后。比如将spring.config.additional.location设置为"optional:classpath:/fkjava/,optional:file:./fkit/",那么SpringBoot加载顺序为:
- optional:classpath:/(类加载路径的根路径)
- optional:classpath:/config/(类加载路径下的/config子目录)
- optional:file:./(当前路径)
- optional:file:./config/(当前路径的/config子目录)
- optional:file:./config/*/(当前路径的/config子目录的任意直接子目录,比如/config/abc、/config/aef等)
- optional:classpath:/fkjava/
- optional:file:./fkit/
前5项是SpringBoot配置文件的默认加载路径
通配符(*)只能在外部路径中使用,例如file前缀就表示使用文件系统的当前路径;classpath前缀的路径不能使用通配符
每个加载路径只能在最后面使用一个通配符
java
@SpringBootApplication
public class App
{
static {
// 设置配置文件的文件名
// 通过系统属性设置SpringBoot配置文件的主文件名可以是application和fk
// 这样SpringBoot就会自动加载application.yml和fk.yml两个配置文件
System.setProperty("spring.config.name", "application, fk");
// 设置配置文件的加载路径
// optional:表示如果classpath下没有指定的配置文件,则不报错,如果不加这个前缀,若该路径下不存在配置文件,就会抛出异常
System.setProperty("spring.config.location",
"classpath:/, optional:classpath:/fk/");
// 设置额外的加载路径
// System.setProperty("spring.config.additional-location",
// "optional:classpath:/fk/");
}
public static void main(String[] args)
{
// 创建Spring容器、运行Spring Boot应用
SpringApplication.run(App.class, args);
}
}
导入额外的配置文件
在spring容器刷新之后,SpringBoot还可使用如下方式导入额外的配置文件
- 使用@PropertySource导入额外的属性文件
- 使用spring.config.import导入额外的配置文件(包括属性文件和Yaml文件)
如果只是为应用中的Bean组件导入一些配置属性,完全可通过上面两种方式来导入。以下示例:
resources\fk\fk.yml文件内容:(只能通过spring.config.import导入)
java
fkjava:
name: "疯狂软件"
age: 25
servers:
- www.fkjava.org
- www.crazyit.org
resources\fk\crazyit.properties文件内容:(spring.config.import和@PropertySource都能导入)
java
crazyit.book.name=Spring Boot
crazyit.book.price=128
resources\applocation.yml文件内容:
java
fkjava:
server:
name: "疯狂软件服务器"
port: 9000
spring:
config:
# 指定导入类加载路径下fk/fk.yml文件
import: optional:classpath:/fk/fk.yml
通过@PropertySource导入crazyit.properties文件示例:
java
@SpringBootApplication
// 导入类加载路径下fk/crazyit.properties文件
@PropertySource("classpath:/fk/crazyit.properties")
public class App
{
public static void main(String[] args)
{
// 创建Spring容器、运行Spring Boot应用
SpringApplication.run(App.class, args);
}
}
使用占位符
配置文件中可使用占位符(${})来引用已定义的属性,或者引用其他配置源(系统属性、环境变量、命令参数等)配置的属性。示例如下:
resources\applocation.yml文件内容:
java
app:
name: "占位符"
# 引用配置文件中的配置属性
description: "${app.name}应用演示了如何在配置文件中使用占位符"
book:
# 引用外部的配置属性
description: ${book.name}是一本非常优秀的图书
server:
# 配置服务端口
port: ${port}
添加命令行参数:--book.name="哈利波特" --port=9090,然后启动应用就会生效。
读取构建文件的属性
SpringBoot允许配置文件读取构建文件(pom.xml或build.gradle)的属性.
只要在pom.xml文件中定义如下元素,就能在配置文件中通过"@属性名@"来引用pom.xml文件中的属性
java
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
</parent>
resources\applocation.yml文件内容:
java
app:
java:
# 引用pom.xml文件中的属性
version: @java.version@
sourceEncoding: @project.build.sourceEncoding@
name: @name@
version: @version@
对于Gradle项目,首先要在build.gradle中对java插件的processResources进行如下配置,然后将这段配置加到build.gradle文件的最后。
java
// 配置Java插件的processResources Task
processResources {
expand(project.properties)
}
接下来即可在配置文件中通过"${属性名}"(这里不再是占位符的表示)的形式引用build.gradle中属性。
resources\applocation.yml文件内容:
java
app:
java:
version: "${sourceCompatibility}"
name: "${rootProject.name}"
version: "${version}"
配置随机值
如果需要为应用配置各种随机值(如随机整数、uuid等),可通过在配置文件中使用${random.xxx}的形式来生成随机值。示例如下:
resources\applocation.yml文件内容:
java
fkjava:
secret: "${random.value}"
number: "${random.int}"
bignumber: "${random.long}"
uuid: "${random.uuid}"
number-less-than-ten: "${random.int(10)}"
number-in-range: "${random.int(20,100)}"
运行结果如下:
如果刷新页面,这些随机值不会发生改变,因为这些随机值是在配置文件中定义的,因此刷新页面并不会改变这些值,只有当应用重启,SpringBoot重新加载配置文件时,才会重新生成这些随机值。