乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@ConfigurationProperties 注解

一句话定位:@ConfigurationProperties 是 Spring Boot 的配置属性批量绑定器。它把散落在 application.yml 或 application.properties 中的扁平化键值对,自动映射到结构化的 Java POJO 中,让配置代码从 @Value("${xxx}") 的碎片化中解放出来。


定义与作用

@ConfigurationProperties 用于将外部配置属性(properties/yaml 文件、环境变量、命令行参数等)批量绑定到带有该注解的 Java Bean 的属性上。它解决了 @Value 注解在复杂配置场景下的两大痛点:

  1. 碎片化:10 个配置项需要写 10 个 @Value,代码冗长且难以维护
  2. 类型不安全:@Value 注入的是字符串,需手动转换复杂类型(如 List、Map、Duration)

与 @Value 的对比

维度@Value@ConfigurationProperties
绑定方式单个属性,逐个注入批量绑定,整体映射
类型支持基础类型 + SpEL支持复杂类型(List、Map、嵌套对象)
校验支持无可配合 @Validated + JSR-303 校验
松散绑定不支持支持(如 first-name 映射到 firstName)
适用场景简单值注入结构化配置对象

松散绑定(Relaxed Binding) 是 @ConfigurationProperties 的杀手级特性:配置中的 spring.datasource.max-pool-size、spring.datasource.maxPoolSize、SPRING_DATASOURCE_MAXPOOLSIZE 环境变量,都能正确映射到 Java 对象的 maxPoolSize 属性。


适用位置与常用属性

适用位置

@ConfigurationProperties 可以标注在:

  1. 类级别:标记该类为配置属性载体,通常与 @Component 或 @Configuration 配合使用
  2. @Bean 方法级别:将第三方库的配置对象注册为 Bean
// 方式一:标注在类上(最常用)
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class StudentProperties { ... }

// 方式二:标注在 @Bean 方法上
@Configuration
public class Config {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() { ... }
}

常用属性

属性类型说明示例
prefixString配置属性的前缀,绑定该前缀下的所有子属性prefix = "feixiang.student"
valueStringprefix 的别名,二者等价value = "spring.datasource"
ignoreInvalidFieldsboolean是否忽略类型转换失败的字段默认 false
ignoreUnknownFieldsboolean是否忽略配置中存在的未知字段默认 true

核心原理

配置属性绑定流程

嵌套结构与类型转换

核心组件:

  • ConfigurationPropertiesBinder:负责执行绑定逻辑
  • RelaxedDataBinder:处理松散绑定(驼峰、中划线、下划线、大写环境变量)
  • ConversionService:将字符串配置值转换为目标字段类型

完整示例

场景简述

飞翔科技公司的学生成绩管理系统需要连接多个外部服务:MySQL 数据库、Redis 缓存、阿里云 OSS。架构师白歌要求小崔把所有连接参数统一管理,并且支持不同环境(开发、测试、生产)使用不同的配置值。

小崔决定用 @ConfigurationProperties 创建一个 FeixiangProperties 配置类,将 application.yml 中的 feixiang.* 前缀属性批量绑定进来。

操作前:使用 @Value 的碎片化配置

// 操作前:传统 @Value 方式(痛点明显)
package com.feixiang.student.config;

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

@Component
public class LegacyConfig {
    
    @Value("${feixiang.student.mysql.host}")
    private String mysqlHost;
    
    @Value("${feixiang.student.mysql.port:3306}")
    private int mysqlPort;
    
    @Value("${feixiang.student.mysql.database}")
    private String mysqlDatabase;
    
    @Value("${feixiang.student.redis.host}")
    private String redisHost;
    
    @Value("${feixiang.student.redis.port:6379}")
    private int redisPort;
    
    @Value("${feixiang.student.oss.endpoint}")
    private String ossEndpoint;
    
    @Value("${feixiang.student.oss.bucket}")
    private String ossBucket;
    
    // 需要写 7 个 @Value,且每新增一个配置都要改代码
    // 没有 IDE 自动补全,拼写错误只能在运行时暴露
}

痛点:

  • 7 个配置项需要 7 个 @Value,代码冗长
  • 新增配置必须修改 LegacyConfig 类
  • 不支持嵌套对象,所有属性都是扁平的
  • 不支持 List/Map 等复杂类型

使用该注解的完整代码

小崔改用 @ConfigurationProperties 后,首先定义结构化的 POJO:

package com.feixiang.student.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;

/**
 * 飞翔科技学生系统配置属性
 * 绑定 application.yml 中 feixiang.student 前缀下的所有属性
 */
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class FeixiangProperties {
    
    private Mysql mysql = new Mysql();
    private Redis redis = new Redis();
    private Oss oss = new Oss();
    private List<String> features;  // 支持 List 类型
    
    // Getters and Setters(必须提供,绑定通过反射调用)
    public Mysql getMysql() { return mysql; }
    public void setMysql(Mysql mysql) { this.mysql = mysql; }
    
    public Redis getRedis() { return redis; }
    public void setRedis(Redis redis) { this.redis = redis; }
    
    public Oss getOss() { return oss; }
    public void setOss(Oss oss) { this.oss = oss; }
    
    public List<String> getFeatures() { return features; }
    public void setFeatures(List<String> features) { this.features = features; }
    
    // 嵌套对象:MySQL 配置
    public static class Mysql {
        private String host;
        private int port = 3306;
        private String database;
        private String username;
        private String password;
        
        // Getters and Setters
        public String getHost() { return host; }
        public void setHost(String host) { this.host = host; }
        public int getPort() { return port; }
        public void setPort(int port) { this.port = port; }
        public String getDatabase() { return database; }
        public void setDatabase(String database) { this.database = database; }
        public String getUsername() { return username; }
        public void setUsername(String username) { this.username = username; }
        public String getPassword() { return password; }
        public void setPassword(String password) { this.password = password; }
    }
    
    // 嵌套对象:Redis 配置
    public static class Redis {
        private String host;
        private int port = 6379;
        private int database = 0;
        
        // Getters and Setters
        public String getHost() { return host; }
        public void setHost(String host) { this.host = host; }
        public int getPort() { return port; }
        public void setPort(int port) { this.port = port; }
        public int getDatabase() { return database; }
        public void setDatabase(int database) { this.database = database; }
    }
    
    // 嵌套对象:OSS 配置
    public static class Oss {
        private String endpoint;
        private String bucket;
        private String accessKey;
        private String secretKey;
        
        // Getters and Setters
        public String getEndpoint() { return endpoint; }
        public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
        public String getBucket() { return bucket; }
        public void setBucket(String bucket) { this.bucket = bucket; }
        public String getAccessKey() { return accessKey; }
        public void setAccessKey(String accessKey) { this.accessKey = accessKey; }
        public String getSecretKey() { return secretKey; }
        public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
    }
}

对应的 application.yml:

# application.yml
feixiang:
  student:
    mysql:
      host: localhost
      port: 3306
      database: student_db
      username: root
      password: feixiang2024
    redis:
      host: localhost
      port: 6379
      database: 1
    oss:
      endpoint: oss-cn-guangzhou.aliyuncs.com
      bucket: feixiang-student-files
      access-key: LTAIxxxxxxxxxxxx
      secret-key: xxxxxxxxxxxxxxxx
    features:
      - cache
      - log
      - monitor

在业务代码中注入使用:

package com.feixiang.student.service;

import com.feixiang.student.config.FeixiangProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SystemConfigService {
    
    private final FeixiangProperties feixiangProperties;
    
    @Autowired
    public SystemConfigService(FeixiangProperties feixiangProperties) {
        this.feixiangProperties = feixiangProperties;
    }
    
    public void printConfig() {
        System.out.println("=== 飞翔科技学生系统配置 ===");
        System.out.println("MySQL: " + feixiangProperties.getMysql().getHost() + ":" 
            + feixiangProperties.getMysql().getPort() + "/" 
            + feixiangProperties.getMysql().getDatabase());
        System.out.println("Redis: " + feixiangProperties.getRedis().getHost() + ":" 
            + feixiangProperties.getRedis().getPort() + "[DB" 
            + feixiangProperties.getRedis().getDatabase() + "]");
        System.out.println("OSS: " + feixiangProperties.getOss().getEndpoint() + "/" 
            + feixiangProperties.getOss().getBucket());
        System.out.println("Features: " + feixiangProperties.getFeatures());
    }
}

操作后运行结果及分析

启动应用并调用 printConfig():

=== 飞翔科技学生系统配置 ===
MySQL: localhost:3306/student_db
Redis: localhost:6379[DB1]
OSS: oss-cn-guangzhou.aliyuncs.com/feixiang-student-files
Features: [cache, log, monitor]

分析:

  1. 批量绑定:application.yml 中 feixiang.student 前缀下的所有属性被一次性绑定到 FeixiangProperties 对象。
  2. 嵌套对象映射:mysql、redis、oss 三个嵌套对象自动实例化并填充属性。
  3. List 类型支持:features 列表被正确解析为 List<String>。
  4. 默认值生效:如果 application.yml 中未指定 mysql.port,则使用 POJO 中定义的 private int port = 3306。

易错场景与面试考点

易错场景一:忘记提供 Setter 方法

小崔最初写 FeixiangProperties 时,为了"代码简洁",只写了 Getter,没写 Setter:

// 错误示范:缺少 Setter
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class BadProperties {
    private String host;
    
    public String getHost() { return host; }
    // 没有 setHost(String host)!
}

后果:Spring 的绑定机制通过反射调用 Setter 注入属性值。缺少 Setter 时,字段保持默认值(null / 0 / false),且不报错。小崔在运行时看到 host 为 null,排查了很久才发现是 Setter 缺失。

正确做法:@ConfigurationProperties 绑定依赖 Setter(或构造器绑定,Spring Boot 2.2+ 支持 @ConstructorBinding)。务必为所有需要绑定的字段提供 Setter。

易错场景二:prefix 拼写错误导致全量空值

// 错误示范:prefix 拼写错误
@Component
@ConfigurationProperties(prefix = "feixiang.stuent")  // ← student 拼成了 stuent
public class FeixiangProperties { ... }

后果:application.yml 中的配置全部在 feixiang.student 前缀下,而 POJO 监听的是 feixiang.stuent。由于 ignoreUnknownFields 默认为 true,框架静默忽略所有配置,POJO 所有字段均为默认值。没有报错,但行为完全错误。

正确做法:

  • 仔细核对 prefix 与 application.yml 中的层级完全一致
  • 在 application.yml 中启用 IDE 的 Spring Boot 配置补全(需引入 spring-boot-configuration-processor)
  • 开发阶段可将 ignoreUnknownFields 设为 false,让未知字段报错

面试考点

Q:@ConfigurationProperties 和 @Value 有什么区别?什么时候用哪个?

@Value 适合单个简单属性的注入,支持 SpEL 表达式;@ConfigurationProperties 适合批量、结构化、复杂类型的配置绑定,支持松散绑定和类型安全。当配置项超过 3 个或存在嵌套结构时,优先使用 @ConfigurationProperties。

Q:松散绑定(Relaxed Binding)是什么意思?

Spring Boot 允许配置键与 Java 属性名以多种形式匹配。例如 Java 中的 maxPoolSize 可以匹配配置中的 max-pool-size、max_pool_size、MAX_POOL_SIZE(环境变量)。这使得不同来源的配置(YAML、properties、环境变量)都能无缝映射到同一 POJO。

Q:如何对 @ConfigurationProperties 进行参数校验?

在 POJO 类上标注 @Validated,在字段上使用 JSR-303 注解(如 @NotNull、@Min(1)、@Pattern)。Spring Boot 会在绑定完成后自动执行校验,校验失败抛出 BindException。

Q:@ConfigurationProperties 的绑定是否支持不可变对象?

Spring Boot 2.2+ 支持 @ConstructorBinding,配合 final 字段和全参构造器,可以创建不可变的配置属性对象。此时不再需要 Setter,但构造器参数名必须与配置键匹配。

上一页
@EnableAutoConfiguration 注解
下一页
@ConditionalOnClass 注解