Spring Boot 配置体系与扩展机制详解:属性注入、加载优先级、SPI 与内嵌容器
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,常见覆盖顺序从低到高是:
-
JAR 内的
application.yml; -
JAR 内的
application-{profile}.yml; -
JAR 外的
application.yml; -
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.location 与 additional-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 通常包含:
-
定义接口;
-
第三方 JAR 提供实现类;
-
在
META-INF/services/接口全限定名中写入实现类名; -
使用
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 | 非阻塞能力较强、配置灵活 | 已验证兼容性的高并发场景 |
参考资料
更多推荐
所有评论(0)