Spring Boot 项目中,配置注入、环境覆盖、自动配置和内嵌服务器都是高频知识点。本文以 Spring Boot 3.x 为背景,讲清 @Value@ConfigurationProperties 的选择、配置属性的注入方式、常用配置优先级、SPI 扩展机制,以及主流内嵌 Servlet 容器。

一、@ConfigurationProperties@Value 的区别

两者都可以从 Spring Environment 中读取配置,但使用目的不同。

1. @Value:注入少量独立配置

app:
  name: order-service
  timeout: 3s
@Component
public class OrderClient {

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

    @Value("${app.timeout:5s}")
    private Duration timeout;
}

${app.timeout:5s} 中的 5s 是默认值。@Value 写法直观,也支持 SpEL,但配置较多时,注解容易散落在多个类中,难以统一管理。

2. @ConfigurationProperties:绑定一组配置

@Validated
@ConfigurationProperties(prefix = "app")
public record AppProperties(
        @NotBlank String name,
        @NotNull Duration timeout) {
}

它可以把具有相同前缀的层级配置批量绑定为类型安全对象,并支持集合、嵌套对象、类型转换和 Bean Validation。

二、Spring Boot 如何注入配置属性?

1. 使用 @Value

如果只有一个开关或少量互不关联的属性,可以直接使用构造器注入:

@Component
public class FeatureService {

    private final boolean enabled;

    public FeatureService(@Value("${feature.enabled:false}") boolean enabled) {
        this.enabled = enabled;
    }
}

相比字段注入,构造器注入便于测试,也能让依赖保持明确。

2. 使用 @ConfigurationProperties

配置文件:

payment:
  base-url: https://pay.example.com
  timeout: 3s
  retry-count: 2

配置对象:

@Validated
@ConfigurationProperties(prefix = "payment")
public record PaymentProperties(
        @NotBlank String baseUrl,
        @NotNull Duration timeout,
        @Min(0) int retryCount) {
}

通过扫描注册:

@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
}

也可以显式注册:

@Configuration
@EnableConfigurationProperties(PaymentProperties.class)
public class PaymentConfig {
}

注册完成后,把它当作普通 Bean 注入:

@Service
public class PaymentService {

    private final PaymentProperties properties;

    public PaymentService(PaymentProperties properties) {
        this.properties = properties;
    }
}

实际选择原则是:单个临时属性使用 @Value;数据库、线程池、第三方接口等成组配置优先使用 @ConfigurationProperties

3. 复杂配置绑定示例

@ConfigurationProperties 的优势在配置结构变复杂后更加明显。例如:

client:
  endpoints:
    - https://api-a.example.com
    - https://api-b.example.com
  pool:
    max-size: 20
    keep-alive: 30s
@ConfigurationProperties(prefix = "client")
public record ClientProperties(
        List<String> endpoints,
        Pool pool) {

    public record Pool(int maxSize, Duration keepAlive) {
    }
}

如果使用多个 @Value 分别读取列表、连接池大小和存活时间,不仅代码分散,字段之间的业务关系也不明显。绑定成配置对象后,可以统一注入、校验和编写测试,还能借助配置元数据获得 IDE 提示。

三、Spring Boot 配置的加载优先级

Spring Boot 会把配置文件、环境变量、系统属性等统一放入 Environment。当同一个键在多个属性源中出现时,高优先级值会覆盖低优先级值。

日常开发最常用的顺序可以从高到低记为:

命令行参数
    ↓
SPRING_APPLICATION_JSON
    ↓
Servlet/JNDI 属性
    ↓
Java 系统属性(-Dkey=value)
    ↓
操作系统环境变量
    ↓
配置文件 application.yml/properties
    ↓
@PropertySource
    ↓
SpringApplication 默认属性

例如:

java -jar app.jar --server.port=9090

命令行参数会覆盖配置文件中的 server.port

配置文件内部的常用优先级

对于 Config Data,常见覆盖顺序从低到高是:

  1. JAR 内的 application.yml

  2. JAR 内的 application-{profile}.yml

  3. JAR 外的 application.yml

  4. JAR 外的 application-{profile}.yml

因此,生产环境可以在 JAR 内保留默认配置,再通过外部 application-prod.yml 覆盖。

例如,JAR 内的默认配置为:

server:
  port: 8080
payment:
  timeout: 5s

部署目录中的 application-prod.yml 可以只覆盖生产差异:

server:
  port: 9000
payment:
  timeout: 2s

启动时激活 prod Profile,最终端口为 9000,超时时间为 2s。如果命令行再传入 --server.port=9100,则最终端口又会被覆盖为 9100。这比单纯背优先级更容易理解:默认值放低优先级位置,环境差异放高优先级位置,一次性临时调整使用命令行。

如果同时存在同位置、同名称的 .properties 和 YAML 文件,官方建议统一格式;两者并存时 .properties 优先。

spring.config.locationadditional-location

  • spring.config.location:替换默认搜索位置;

  • spring.config.additional-location:在默认位置之外继续添加位置。

java -jar app.jar \
  --spring.config.additional-location=optional:file:./custom-config/

optional: 表示文件不存在时仍允许启动。排查配置为什么没有生效时,应同时检查属性源优先级、激活的 Profile、外部文件位置以及命令行和环境变量。

四、什么是 Spring Boot 的 SPI 机制?

SPI,即 Service Provider Interface,是“定义接口,由外部实现并在运行时发现”的扩展思想。

1. Java 原生 SPI

Java SPI 通常包含:

  1. 定义接口;

  2. 第三方 JAR 提供实现类;

  3. META-INF/services/接口全限定名 中写入实现类名;

  4. 使用 ServiceLoader 加载实现。

JDBC 驱动等场景体现了类似的插件化思想。

2. Spring 的扩展加载

Spring 提供 SpringFactoriesLoader,能够从约定的元数据文件中加载实现。它和 Java ServiceLoader 的目标相似,但属于 Spring 自己的工厂加载体系。

3. Spring Boot 自动配置发现

Spring Boot 的自动配置也具有 SPI 特征:Starter JAR 声明自动配置候选,应用启动时发现并按条件注册 Bean。

在 Spring Boot 3 中,自定义自动配置类通常使用:

@AutoConfiguration
@ConditionalOnClass(MyClient.class)
@ConditionalOnMissingBean(MyClient.class)
public class MyClientAutoConfiguration {

    @Bean
    MyClient myClient() {
        return new MyClient();
    }
}

并在下面的文件中登记:

META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容为自动配置类的全限定名:

com.example.autoconfigure.MyClientAutoConfiguration

应用引入该 Starter 后,Spring Boot 读取候选配置,再根据 @ConditionalOnClass@ConditionalOnMissingBean 等条件决定是否生效。这就是“引入依赖即可获得默认 Bean,同时允许用户覆盖”的核心原理。

五、Spring Boot 支持哪些主流内嵌 Servlet 容器?

Spring Boot 3.x Servlet Web 应用常用三种内嵌容器。

1. Tomcat

Tomcat 是 spring-boot-starter-web 的默认选择,生态成熟、资料丰富,兼容性最好,适合大多数项目。

2. Jetty

Jetty 体积相对轻量,嵌入能力强。替换时先排除 Tomcat,再引入 Jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

3. Undertow

Undertow 采用较灵活的非阻塞架构,也可以作为 Spring Boot 3.x 的内嵌 Servlet 容器。替换方式与 Jetty 类似,引入 spring-boot-starter-undertow。具体可用版本应与项目使用的 Spring Boot 版本兼容。

容器特点建议场景
Tomcat默认、成熟、生态完善大多数业务系统
Jetty轻量、嵌入友好对 Jetty 有既有经验的项目
Undertow非阻塞能力较强、配置灵活已验证兼容性的高并发场景

参考资料

更多推荐