SpringBoot--如何给项目添加配置属性及读取属性

SpringBoot允许使用配置文件对应用程序进行配置,支持以下不同形式的配置源:

  1. 属性文件(比如application.properties)
  2. yaml文件(后缀可以是yml或者yaml)
  3. 环境变量
  4. 命令行参数

获取这些外部化属性有以下几种方式:

  1. 使用@Value注解
  2. 使用Spring的Environment抽象层进行访问
  3. 使用@ConfigurationProperties注解将一批特定属性绑定到java对象

配置源的加载顺序和优先级

由于先加载的配资源都有可能被后加载的配资源覆盖,因此认为后加载的配置源优先级更高,加载顺序如下:

  1. 默认属性(通过SpringApplication.setDefaultProperties()方法指定)
  2. 配置类(@Configuration修饰的类)上用@PropertySource注解加载的属性文件中的属性值
  3. 配置数据(如application.properties文件等)
  4. RandomValuePropertySource,只包含random.*中的属性
  5. 操作系统环境变量
  6. java系统属性(System的getProperties()方法返回的属性)
  7. 来自java:comp/env的jndi属性(只在Web服务器或应用服务器中有用)
  8. ServletContext的初始化参数(在Web.xml文件中通过<context-param.../>元素设置的初始化参数)(只在Web服务器或应用服务器中有用)
  9. ServletConfig的初始化参数(在Web.xml文件中通过<init-param.../>元素设置的初始化参数,或者通过@Servlet注解设置的初始化参数)
  10. 来自SPRING_APPLICATION_JSON的属性(嵌套在环境变量或系统属性中的JSON文本)
  11. 命令行参数
  12. 测试用例类上通过@SpringBootTest注解的properties所指定的属性(仅在单元测试生效)
  13. 测试用例类上用@TestPropertySource注解加载的属性文件中的属性值(仅在单元测试生效)
  14. 当SpringBoot的devtools工具处于激活状态时,用户Home目录中.config/spring-boot/子目录下spring-boot-devtools.properties或spring-boot-devtools.yml文件中设置的属性值(windows平台对应"C:\User\用户名"下的配置文件)

此外application.properties(包括application.yml)也有几个不同的来源,它们按如下顺序加载:

  1. jar包内的application.properties(或application.yml)
  2. jar包内的application-{profile}.properties(或application-{profile}.yml)
  3. jar包外临时指定的application.properties(或application.yml)
  4. 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文件配置的属性,可使用以下工具类

  1. YamlPropertiesFactoryBean,它将YAML文件加载为Properties对象
  2. YamlMapFactoryBean,它将YAML文件加载为Map对象
  3. 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或按如下顺序加载配置文件,后加载的文件会覆盖先加载的文件,因此,后加载的文件有更高的优先级

  1. 类加载路径的根路径
  2. 类加载路径下的/config子目录
  3. 当前路径
  4. 当前路径的/config子目录
  5. 当前路径的/config子目录的任意直接子目录,比如/config/abc、/config/aef等

如果想改变这种默认行为,可通过如下系统属性(或环境变量、或命令行参数)来设置

  1. spring.config.name:改变配置文件的文件名,默认是application。如果用OS环境变量来设置,则属性对应于SPRING_APPLICATION_NAME环境变量名。
  2. spring.config.location:改变配置文件的加载路径。如果用OS环境变量来设置,则属性对应于SPRING_APPLICATION_LOCATION环境变量名。
  3. spring.config.additional.location:添加配置文件的加载路径,不会覆盖原来的配置文件的加载路径。

不要使用SpringBoot配置属性来设置以上三种属性,因为这几个属性的加载实际非常早,只能通过系统环境变量、系统属性或命令行参数来设置。

通过spring.config.location或spring.config.additional.location指定的加载路径是有先后顺序的,比如spring.config.location设置为"optional:classpath:/fkjava/,optional:file:./fkit/",那么加载顺序为:

  1. optional:classpath:/fkjava/
  2. optional:file:./fkit/

如果使用spring.config.additional.location来添加配置文件的加载路径,那么新增路径总是排在默认的加载路径之后。比如将spring.config.additional.location设置为"optional:classpath:/fkjava/,optional:file:./fkit/",那么SpringBoot加载顺序为:

  1. optional:classpath:/(类加载路径的根路径)
  2. optional:classpath:/config/(类加载路径下的/config子目录)
  3. optional:file:./(当前路径)
  4. optional:file:./config/(当前路径的/config子目录)
  5. optional:file:./config/*/(当前路径的/config子目录的任意直接子目录,比如/config/abc、/config/aef等)
  6. optional:classpath:/fkjava/
  7. 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还可使用如下方式导入额外的配置文件

  1. 使用@PropertySource导入额外的属性文件
  2. 使用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重新加载配置文件时,才会重新生成这些随机值。

相关推荐
胚芽鞘6814 小时前
关于java项目中maven的理解
java·数据库·maven
岁忧5 小时前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
CJi0NG5 小时前
【自用】JavaSE--算法、正则表达式、异常
java
Hellyc5 小时前
用户查询优惠券之缓存击穿
java·redis·缓存
今天又在摸鱼5 小时前
Maven
java·maven
老马啸西风6 小时前
maven 发布到中央仓库常用脚本-02
java·maven
代码的余温6 小时前
MyBatis集成Logback日志全攻略
java·tomcat·mybatis·logback
一只叫煤球的猫7 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员
斐波娜娜7 小时前
Maven详解
java·开发语言·maven
Bug退退退1237 小时前
RabbitMQ 高级特性之事务
java·分布式·spring·rabbitmq