深入Spring Boot配置机制:如何高效管理应用配置

一、属性的优先级和配置文件的位置

在Spring Boot应用中,我们可以在多个地方定义配置属性,并且Spring Boot提供了一套优先级排序,来决定同一属性多处定义时的覆盖关系。理解这个机制对于配置管理至关重要。

1. 配置文件查找的顺序

Spring Boot在启动时会按照一定的顺序查找并加载配置文件,这个顺序大致如下:  

1. 首先从jar外部的/config子目录里的配置文件读取配置。

2. 然后是jar相同目录下(./)的配置文件。

3. 接着是jar内部的/config包里的配置文件。

4. 最后是jar内部的配置文件(application.properties或application.yml)。

注:在同一目录级别中,优先加载后缀名为.yml或.yaml的文件,然后才是.properties文件。

2. 属性来源的优先级

Spring Boot的属性配置具有以下的优先级顺序,从最高优先级到最低优先级排序如下:

1. 命令行参数

2. 来自SPRING_APPLICATION_JSON的属性

3. ServletConfig初始化参数

4. ServletContext初始化参数

5. JNDI属性

6. Java系统属性(使用System.getProperties()获取)

7. 操作系统环境变量

8. 随机生成的 random.* 属性

9. JAR包外部的application-{profile}.properties或application-{profile}.yml(带Spring profiles的配置)

10. JAR包内部的application-{profile}.properties或application-{profile}.yml

11. JAR包外部的application.properties和application.yml

12. JAR包内部的application.properties和application.yml

13. @Configuration类上的@PropertySource注解

14. 默认属性(使用@Value时的默认值)

3. 外部配置的作用

外部配置可用于运行的应用程序,无需对代码进行任何更改。通过命令行参数、环境变量等,我们可以轻易地在不同环境间切换配置,这对于多环境部署非常有用。

二、使用application.properties或application.yml

Spring Boot的配置文件application.properties和application.yml在项目的资源目录下是极其常见的。这两个文件为应用的运行提供了必要的配置参数,并且Spring Boot在启动的时候会自动加载它们。

1. 基础配置读取实例

下面是如何在Spring Boot应用中读取properties配置的例子:

server.port=8080
app.name=MyApp

在Java代码中,我们可以使用@Value注解来读取这些值:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    @Value("${server.port}")
    private int serverPort;

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

    // getter and setter methods
}

2. 配置文件分环境配置方法

通常,在开发、测试和生产等不同环境中,应用会有不同的配置需求。Spring Boot允许我们通过建立命名为application-{profile}.properties或application-{profile}.yml的文件来定义特定环境的配置。 例如,如果你有针对开发环境的特定配置,你可以创建一个application-dev.properties文件:

server.port=8081
app.debug=true

在启动Spring Boot应用时,可以通过下面的方式指定激活哪个profiles:

java -jar myapp.jar --spring.profiles.active=dev

这样就会加载application-dev.properties中的配置,同时application.properties中的配置也会被加载,但application-dev.properties的配置会覆盖application.properties的同名配置。

三、使用@Value注解读取配置

使用@Value注解是读取单个配置值的最直接方法。

1. 单个属性读取

以下是如何使用@Value注解来读取配置文件中的单个属性值的例子:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ServiceProperties {

    @Value("${service.endpoint}")
    private String serviceEndpoint;

    // getter and setter if needed
}

在上面的代码中,${service.endpoint}将替换为在配置文件中指定的service.endpoint属性的值。如果该属性不存在,则注解的值会是一个空字符串。

2. 配置文件动态刷新

@Value注解并不支持动态更新配置,如果需要实现这样的功能,我们通常会结合Spring Cloud Config 或 Spring's Environment抽象来使用。但如果项目中已经使用了@Value,我们可以使用@RefreshScope注解来标注需要动态配置的bean。例如:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

@Component
@RefreshScope
public class DynamicConfig {

    @Value("${dynamic.value}")
    private String dynamicValue;

    // getter and setter if needed
}

四、使用@ConfigurationProperties读取配置

@ConfigurationProperties注解是Spring Boot中用于强类型安全配置的替代方案,比@Value注解提供了更多的优势,如类型安全、松散绑定和复杂类型嵌套。

1. 绑定配置到POJO

通过创建一个带有@ConfigurationProperties注解的类,我们可以将配置项绑定到POJO的属性上。下面是一个例子:

app:
  mail:
    host: smtp.example.com
    port: 587
    from: no-reply@example.com
java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix="app.mail")
public class MailProperties {

    private String host;
    private int port;
    private String from;

    // standard getters and setters
}

在上述例子中,@ConfigurationProperties(prefix="app.mail")指明了所有以app.mail为前缀的配置项都会绑定到相应的属性上。

2. 松散绑定与属性验证

松散绑定(Relaxed Binding)是Spring Boot的一个特性,它允许属性名的不完全匹配,例如在配置文件中定义app.mailPort,而在POJO类中则可以用mailPort或mail_port来绑定这个值。@ConfigurationProperties也支持JSR 303验证注解,比如@NotNull,你可以在你的配置POJO上添加校验注解来确保配置值满足某些条件,例如:

import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;

@Component
@ConfigurationProperties(prefix="app.user")
@Validated
public class UserProperties {

    @NotBlank
    private String username;

    // standard getters and setters
}

使用@Validated注解,可以确保在应用启动时如果配置项不符合验证条件,应用将不会启动,并提供错误提示。

五、使用@PropertySource注解读取指定配置

在Spring Boot中,默认情况下,配置是从application.properties或application.yml文件中读取的。但有时我们需要从其他资源位置加载配置,这时@PropertySource注解就显得非常有用。

1. 加载额外的配置文件

例如,你有一些特定的配置文件需要和主配置文件分开管理,这时可以创建一个带有@PropertySource注解的@Configuration类来指定配置文件的位置。下面是如何使用@PropertySource来加载指定配置的一个例子:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:extras.properties")
public class ExtraConfig {

    // ...
}

@PropertySource注解中的字符串指向你的配置文件相对于类路径的位置。除了类路径之外,还可以使用file:(文件系统路径)或url:(访问网络资源)前缀来指定资源的位置。

2. 理解@PropertySource的使用场景

@PropertySource很适合用于加载不经常变动的基础配置,如数据库连接信息、客户端库配置等。此注解通常和@ConfigurationProperties搭配使用,用于在自定义配置类中指定多个配置文件。 需要注意的是,@PropertySource注解并不支持YAML格式的文件,它只能用于属性(properties)文件。如果需要加载YAML文件,你需要考虑其他方法,比如Spring Boot的配置文件分环境特性或使用Spring Cloud Config。

六、使用环境变量和命令行参数

在现代应用程序中,特别是在云环境和容器化部署中,使用环境变量和命令行参数来覆盖配置文件中的默认值变得越来越普遍。Spring Boot提供了灵活的支持,让这两种方式成为配置管理不可或缺的部分。

1. 读取系统环境变量

系统环境变量可以通过spring.environment属性或直接在application.properties/application.yml中使用通配符${VARIABLE_NAME}来读取。例如,如果你想通过环境变量来设置应用的端口,可以在application.yml中这样配置:

server:
  port: ${PORT:8080}

这里${PORT:8080}表示如果环境变量PORT被定义,其值将被用作服务器端口;如果未定义,将使用默认值8080。

2. 读取JVM系统属性

通过命令行启动Spring Boot应用时,可以通过-D标志设置JVM系统属性,这些属性也可以被用来覆盖配置。例如,你可以在启动应用时设置系统属性:

java -Dserver.port=8090 -jar myapp.jar

这将会覆盖application.properties中关于server.port的配置,使应用运行在8090端口上。

3. 读取命令行参数

命令行参数是通过应用启动时传递给main方法的参数,并且Spring Boot会自动解析这些参数以覆盖配置文件中的值。例如,启动应用时可以这样指定:

java -jar myapp.jar --server.port=8091

命令行参数的优先级高于环境变量,这意味着如果同时通过环境变量和命令行参数指定了配置,命令行参数会生效。 通过这些方式,Spring Boot应用可以轻松适应不同环境下的配置需求,无论是在本地开发还是在云平台上部署。

七、结合Cloud Config 读取远程配置文件

随着云原生应用的兴起,中心化管理应用配置成为了一种越来越受欢迎的实践。Spring Cloud Config为Spring Boot应用提供了一个解决方案,允许从远程位置(如Git仓库、文件系统等)统一管理配置文件,其它优秀的开源配置中心Apollo、Nacos也强烈推荐使用。

1. Spring Cloud Config 简介

Spring Cloud Config分为客户端和服务器两部分。服务器端存放集中化的配置文件,而客户端则是运行中的Spring Boot应用,它从Config Server读取配置。Config Server支持多种后端存储配置文件,其中最常见的是Git。 要使用Spring Cloud Config,首先需要搭建一个Config Server,并指定配置文件的存储位置。以下是一个简单的Config Server配置示例:

# application.yml of Config Server
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/example/config-repo.git

在客户端应用中,需要指定Config Server的位置:

# bootstrap.properties of Config Client
spring.cloud.config.uri=http://localhost:8888

客户端应用启动时,会从指定的Config Server读取配置。通过这种方式,可以在不重新部署服务的情况下,动态更改应用的配置。

2. 远程配置文件的读取和使用

为了确保你的Spring Boot应用从Spring Cloud Config Server正确加载配置,你需要在客户端应用的bootstrap.properties或bootstrap.yml文件中进行一些基础设置。这通常包括Config Server的URI、应用名等。然后,你可以像使用本地配置文件一样使用这些远程配置。Spring Cloud Config允许按环境分隔配置文件,你只需在客户端的bootstrap.properties文件中指定活动的profiles:

spring.application.name=myapp
spring.profiles.active=dev
spring.cloud.config.uri=http://localhost:8888

这样,当启动客户端应用时,它会从Config Server获取名为myapp-dev.yml或myapp-dev.properties的配置文件。

八、多配置文件的读取策略

在复杂的应用环境中,经常需要根据不同的环境(如开发、测试和生产)来分开管理配置文件。Spring Boot通过profiles提供了一种强大的方式来实现这一目标,同时也支持通过spring.config.import指令来合并多个配置文件。

1. @Profile注解在配置读取中的应用

Profiles为不同环境下的配置提供了一种条件化的方法。你可以创建专门针对某个环境的配置文件,例如application-dev.yml、application-test.yml等,并通过设置活动的profiles来激活相应的配置文件。 你也可以使用@Profile注解在代码级别定义配置的条件化,这允许你根据不同的环境来激活或禁用特定的Bean。例如:

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("dev")
public class DevDatabaseConfig {
    //...
}

这样,上面的DevDatabaseConfig类只有在“dev”profile被激活时才会被注册。

2. 使用spring.config.import指令

Spring Boot 2.4引入了spring.config.import属性,它提供了一种新的方式来合并和导入配置文件。使用这个属性,你可以轻松地引入其他配置文件或配置资源,它们可以是本地文件,也可以是远程位置的文件。你可以这样使用spring.config.import来引入额外的配置文件:

spring:
  config:
    import: classpath:extra-config.yml
展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java