springboot注解(四)

十六、@ConditionalOnResource

@ConditionalOnResource 是 Spring Boot 提供的一个条件注解(Conditional Annotation) ,用于根据 类路径(classpath)或文件系统中是否存在指定资源 来决定是否加载某个配置类、Bean 或自动配置组件。

16.1 核心作用

只有当指定的资源(如配置文件、数据文件、脚本等)存在时,被注解的类或方法才会生效。

这在需要"按需加载"功能模块(例如:仅当存在 schema.sql 时才初始化数据库)时非常有用。

16.2 基本用法

java 复制代码
@ConditionalOnResource(resources = "classpath:my-config.properties")
public class MyConfig {
    // 仅当 classpath 下存在 my-config.properties 时加载
}

支持多个资源(逻辑 AND 关系):

java 复制代码
@ConditionalOnResource(resources = {
	"classpath:config/app.properties",
	"file:/opt/myapp/data.txt"
})

所有指定的资源都必须存在,条件才成立。

16.3 资源路径格式

Spring 使用 ResourceLoader 解析资源,支持以下前缀:

前缀 说明 示例
classpath: 从类路径加载 classpath:application-prod.yml
file: 从文件系统绝对路径加载 file:/etc/myapp/config.json
无前缀 默认按 classpath: 处理(但不推荐,建议显式指定) my-script.sql → 等价于 classpath:my-script.sql

⚠️ 注意:相对路径(如 ./data.txt)行为不确定,应避免使用。

16.4 典型应用场景

✅ 场景 1:仅当存在 SQL 脚本时初始化数据库
java 复制代码
@Configuration
@ConditionalOnResource(resources = "classpath:schema.sql")
public class DatabaseInitConfig {
    @Bean
    public DataSourceInitializer initializer(DataSource dataSource) {
        // 自动执行 schema.sql 和 data.sql
    }
}

Spring Boot 内置的 DataSourceAutoConfiguration 就使用了类似逻辑。


✅ 场景 2:可选的外部配置文件
java 复制代码
// 仅当用户提供了自定义 banner 文件时启用
@ConditionalOnResource(resources = "file:${user.home}/.myapp/banner.txt")
public class CustomBannerConfig {
    @Bean
    public Banner banner() {
        return new ResourceBanner(new FileSystemResource(
            System.getProperty("user.home") + "/.myapp/banner.txt"));
    }
}

✅ 场景 3:模块化插件机制

假设你的应用支持插件,插件需提供 plugin-definition.json

java 复制代码
@ConditionalOnResource(resources = "classpath:plugin-definition.json")
public class PluginAutoConfig {
    // 加载并注册插件
}

只要 JAR 包里包含该文件,插件自动激活。

16.5.注意事项

  1. 资源必须"存在且可读"
    不仅要路径正确,还要求应用有读取权限(尤其对 file: 资源)。
  2. 不支持通配符或目录检测
    不能写 classpath:configs/*.propertiesclasspath:mydir/
    如需检测目录,需自定义 Condition
  3. 多个资源是 AND 关系
    所有资源都必须存在。如需 OR 逻辑,需自定义条件。
  4. 性能影响极小
    资源检测只在启动时执行一次,使用 Resource.exists() 判断。
  5. 与 @PropertySource 配合使用需谨慎
    如果先用 @ConditionalOnResource 检查文件存在,再用 @PropertySource 加载,顺序很重要(条件判断先于属性加载)。

16.6 底层原理

@ConditionalOnResource 基于 @Conditional(OnResourceCondition.class) 实现。
OnResourceCondition 会:

  1. 使用 ConditionContext.getResourceLoader() 获取 ResourceLoader
  2. 对每个 resources 路径调用 resource.exists()
  3. 全部返回 true 则条件成立

16.7 对比其他条件注解

注解 判断依据
@ConditionalOnResource 资源文件是否存在
@ConditionalOnProperty 配置属性值是否匹配
@ConditionalOnClass 类路径是否存在某个
@ConditionalOnBean Spring 容器中是否存在某个 Bean

💡 @ConditionalOnResource 关注的是文件/资源 ,而 @ConditionalOnClass 关注的是Java 类


16.8 总结

@ConditionalOnResource 是实现 "资源驱动配置" 的关键注解,适用于:

  • 按需初始化(如数据库脚本)
  • 可选外部配置
  • 插件化架构
  • 环境差异化部署(如生产环境才有某些密钥文件)

通过它,你可以让 Spring Boot 应用更加灵活、安全、自适应,避免因缺少资源导致启动失败或功能异常。

十七、@ConditionalOnJndi

@ConditionalOnJndi 是 Spring Boot 提供的一个条件注解(Conditional Annotation) ,用于根据 JNDI(Java Naming and Directory Interface)环境是否可用 来决定是否加载某个配置类、Bean 或自动配置组件。

17.1 核心作用

只有当应用运行在支持 JNDI 的环境中(如传统 Java EE 应用服务器:Tomcat、WebLogic、JBoss/WildFly 等),并且可以成功查找指定的 JNDI 名称时,被注解的配置才会生效。

这主要用于兼容部署在应用服务器中的场景,例如从 JNDI 获取数据源(DataSource)、JMS 连接工厂等资源。

17.2 基本用法

1. 检查 JNDI 环境是否可用(不指定具体名称)
java 复制代码
@Configuration
@ConditionalOnJndi
public class JndiBasedConfig {
    // 仅当 JNDI 环境可用时加载
}

此时只要 InitialContext 能成功初始化(即运行在支持 JNDI 的容器中),条件就成立。

2. 检查特定 JNDI 名称是否存在
java 复制代码
@Configuration
@ConditionalOnJndi("java:comp/env/jdbc/mydb")
public class DatabaseConfig {
    // 仅当 JNDI 名称 java:comp/env/jdbc/mydb 可查找到时生效
}

支持多个 JNDI 名称(逻辑 OR 关系):

java 复制代码
@ConditionalOnJndi({"java:comp/env/jdbc/db1", "java:comp/env/jdbc/db2"})

只要其中一个 JNDI 名称存在,条件就成立。

17.3 典型应用场景

✅ 场景 1:从 JNDI 获取 DataSource(传统企业部署)

在 WebLogic 或 Tomcat 中配置了 JNDI 数据源:

xml 复制代码
<!-- Tomcat context.xml -->
<Resource name="jdbc/mydb" auth="Container" type="javax.sql.DataSource" ... />

Spring Boot 应用中:

java 复制代码
@Bean
@ConditionalOnJndi("java:comp/env/jdbc/mydb")
public DataSource dataSource() throws NamingException {
    InitialContext ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/mydb");
}

这样,当应用部署到支持 JNDI 的服务器时自动使用 JNDI 数据源;

若打包为可执行 JAR(无 JNDI 环境),则跳过此配置,可 fallback 到 HikariCP 等内嵌数据源。

✅ 场景 2:区分云原生与传统部署
  • 云原生(Spring Boot 内嵌 Tomcat)→ 无 JNDI → 使用 application.yml 配置数据库
  • 传统部署(WebLogic)→ 有 JNDI → 使用 JNDI 数据源

通过 @ConditionalOnJndi 实现同一套代码两种部署模式

17.4 底层原理

@ConditionalOnJndi 基于 @Conditional(OnJndiCondition.class) 实现。

其判断逻辑如下:

  1. 尝试创建 javax.naming.InitialContext
  2. 如果失败(抛出 NamingExceptionNoClassDefFoundError),说明 JNDI 不可用
  3. 如果指定了 JNDI 名称,则进一步调用 context.lookup(name) 测试是否存在
  4. 所有指定名称中至少一个存在,或未指定名称但 JNDI 环境可用 → 条件成立

⚠️ 注意:在 Spring Boot 默认的可执行 JAR(内嵌 Tomcat)中,JNDI 默认是禁用的 ,因此 @ConditionalOnJndi 通常不会生效。

17.5 注意事项

  1. Spring Boot 内嵌容器默认不启用 JNDI
    即使使用内嵌 Tomcat,也需要显式启用 JNDI(不推荐):
java 复制代码
   TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
   factory.setUseJndi(true);
  1. 主要用于传统 Java EE 迁移场景
    在现代云原生架构中,JNDI 已逐渐被配置中心、环境变量等替代。
  2. 与 @Profile 结合使用更安全
    可配合 profile 区分环境:
java 复制代码
   @Configuration
   @Profile("prod-legacy")
   @ConditionalOnJndi("java:comp/env/jdbc/mydb")
   public class LegacyJndiConfig { }
  1. 性能影响可忽略
    JNDI 检测只在启动时执行一次。

17.6 与其他条件注解对比

注解 适用场景
@ConditionalOnJndi 运行在 Java EE 应用服务器,需从 JNDI 获取资源
@ConditionalOnProperty 基于配置文件开关
@ConditionalOnResource 基于文件/资源是否存在
@ConditionalOnClass 基于类路径是否存在某类

总结

@ConditionalOnJndi 是 Spring Boot 为兼容传统企业级部署环境而提供的条件注解,适用于:

  • 从 JNDI 获取数据源、JMS、EJB 等资源
  • 同一套代码同时支持 云原生(无 JNDI)传统应用服务器(有 JNDI) 部署
  • 平滑迁移老旧 Java EE 应用到 Spring Boot

虽然在现代微服务架构中使用较少,但在企业遗留系统整合或混合部署场景中仍具有重要价值。

17.7 什么是JNDI

JNDI(Java Naming and Directory Interface ,Java 命名和目录接口)是 Java 提供的一套标准 API ,用于访问命名服务(Naming Service)目录服务(Directory Service) 。它的核心作用是:通过名字查找资源,实现"解耦"------应用程序不需要知道资源的具体位置或实现细节,只需通过一个逻辑名称即可获取。


17.7.1 通俗理解:JNDI 就像"电话簿"或"DNS"

  • 你不需要记住朋友的手机号(IP 地址、数据库连接串等),只需查"张三"这个名字,电话簿(JNDI)就告诉你号码。
  • 在企业应用中,数据库、消息队列、EJB 等资源都被注册到 JNDI 中,程序通过名字(如 java:comp/env/jdbc/mydb)去"查号",拿到实际对象。

17.7.2 JNDI 能做什么?

功能 说明 示例
查找资源 通过名字获取对象引用 获取数据库连接池(DataSource)
绑定资源 将对象注册到命名系统 应用服务器启动时把 DataSource 绑定到 JNDI
列出上下文 浏览命名空间结构 查看当前有哪些 JNDI 名称可用

17.7.3 典型应用场景(企业级开发)

✅ 1. 获取数据库连接(最常见)

在传统 Java EE 应用服务器(如 Tomcat、WebLogic、JBoss)中:

  1. 管理员在服务器上配置一个数据源(DataSource),并给它起个 JNDI 名字,比如:

    text 复制代码
    java:comp/env/jdbc/MyDB
  2. 开发者 在代码中通过 JNDI 查找这个数据源,而不需要硬编码数据库 URL、用户名、密码

java 复制代码
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
Connection conn = ds.getConnection();

✅ 好处:

  • 数据库配置由运维管理,代码无需修改
  • 支持连接池、高可用等企业级特性
  • 安全(密码不在代码或配置文件中)

✅ 2. 查找 EJB(Enterprise JavaBeans)

在 Java EE 中,远程业务组件(EJB)也通过 JNDI 发布:

java 复制代码
MyService service = (MyService) ctx.lookup("java:global/myapp/MyServiceImpl");

✅ 3. 集成 LDAP 目录服务

JNDI 也支持 LDAP(轻量目录访问协议),可用于:

  • 用户认证(如公司 AD 域)
  • 查询组织架构
java 复制代码
DirContext ctx = new InitialDirContext(env);
Attributes attrs = ctx.getAttributes("uid=john,ou=people,dc=example,dc=com");

17.7.4 JNDI 的命名结构(Name Syntax)

JNDI 名称通常是分层的 URI 风格,例如:

复制代码
  java:comp/env/jdbc/mydb
  • java::协议(表示 Java 命名空间)
  • comp:组件私有命名上下文
  • env:环境条目(Environment Entries)
  • jdbc/mydb:自定义资源名

常见前缀:

  • java:comp/:当前组件私有
  • java:global/:全局可见
  • java:jboss/(JBoss 特有)

17.7.5 JNDI 与 Spring / Spring Boot 的关系

  • 传统 Java EE 应用:重度依赖 JNDI。
  • Spring Boot(默认)不使用 JNDI ,而是通过 application.propertiesapplication.yml 配置数据源等资源。
  • 但 Spring Boot 仍支持 JNDI :通过 @ConditionalOnJndi 等机制,允许在部署到 WebLogic/Tomcat 等服务器时自动切换到 JNDI 模式,实现"一套代码,两种部署"。

17.7.6 为什么现代应用很少提 JNDI?

  1. 云原生兴起:微服务、容器化(Docker/K8s)更倾向通过环境变量、配置中心管理资源。
  2. Spring Boot 内嵌容器:默认使用 HikariCP 等内嵌连接池,无需应用服务器。
  3. JNDI 配置复杂:需在应用服务器层面配置,不利于 DevOps 自动化。

🔸 但在银行、电信等传统行业,大量系统仍运行在 WebLogic/WebSphere 上,JNDI 仍是标配。

17.7.7 简单代码示例(Tomcat 中使用 JNDI)

  1. 在 context.xml 中定义数据源
xml 复制代码
<Context>
  <Resource name="jdbc/mydb"
            auth="Container"
            type="javax.sql.DataSource"
            driverClassName="com.mysql.cj.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/test"
            username="root"
            password="secret" />
</Context>
  1. 在 web.xml 中引用
xml 复制代码
<resource-ref>
  <res-ref-name>jdbc/mydb</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>
  1. Java 代码中获取
java 复制代码
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mydb");

十八、@ConditionalOnCloudPlatform

@ConditionalOnCloudPlatform 是 Spring Boot 提供的一个条件注解(Conditional Annotation) ,用于根据 应用程序是否运行在特定的云平台(Cloud Platform)上 来决定是否加载某个配置类、Bean 或自动配置组件。

18.1 核心作用

只有当应用部署在指定的云平台(如 Cloud Foundry、Kubernetes、Heroku 等)时,被注解的配置才会生效。

这使得开发者可以针对不同云环境提供差异化配置,例如:

  • 在 Kubernetes 中使用 ConfigMap 加载配置
  • 在 Cloud Foundry 中绑定服务实例
  • 在本地开发时不启用云相关功能

18.2 支持的云平台(CloudPlatform 枚举)

Spring Boot 内置识别以下云平台(截至 Spring Boot 2.4+):

平台 枚举值 检测依据
Cloud Foundry CLOUD_FOUNDRY 存在环境变量 VCAP_APPLICATION
Kubernetes KUBERNETES 存在文件 /var/run/secrets/kubernetes.io/serviceaccount/namespace(即 Pod 运行环境)
Heroku HEROKU 存在环境变量 DYNO
SAP Cloud Platform SAP 存在环境变量 HCLOUDSAP_JAVA_BUILDPACK
AWS (Amazon Web Services) AWS 存在环境变量 EC2_HOME 或能访问 EC2 元数据服务(需网络权限)
Azure AZURE 存在环境变量 WEBSITE_INSTANCE_ID(Azure App Service)
Google Cloud Platform (GCP) GCP 存在环境变量 GOOGLE_CLOUD_PROJECT 或元数据服务可达

⚠️ 注意:

  • Spring Boot 不会主动探测所有平台 ,而是基于环境变量、文件系统或元数据服务进行轻量判断。
  • 如果未明确运行在任何已知云平台,则视为 NONE

18.3 基本用法

1. 指定单一云平台
java 复制代码
@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
public class K8sConfig {
    // 仅在 Kubernetes 中生效
}
2. 结合其他条件使用(常见)
java 复制代码
@Bean
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
@ConditionalOnProperty(name = "cf.service.enabled", havingValue = "true")
public MyService cfService() {
    return new CloudFoundryMyService();
}

18.4 典型应用场景

✅ 场景 1:Kubernetes 特有配置

在 K8s 中,常通过 ConfigMap 或 Secret 注入配置:

java 复制代码
@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
public class K8sSecretConfig {

    @Bean
    public DatabaseCredentials dbCredentials() {
        // 从 /etc/secrets/db-password 读取密码
        String password = Files.readString(Paths.get("/etc/secrets/db-password"));
        return new DatabaseCredentials("user", password);
    }
}
✅ 场景 2:Cloud Foundry 服务绑定

Cloud Foundry 通过 VCAP_SERVICES 环境变量传递服务信息:

java 复制代码
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public class CfDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // 解析 VCAP_SERVICES 获取数据库连接信息
        String vcap = System.getenv("VCAP_SERVICES");
        Map<String, Object> services = parseVcap(vcap);
        // ... 构建 DataSource
    }
}
✅ 场景 3:禁用本地开发时的云功能
java 复制代码
// 仅在非云环境(如本地)启用 mock 服务
@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.NONE)
public class LocalMockConfig {
    @Bean
    public EmailService emailService() {
        return new MockEmailService(); // 不真实发邮件
    }
}

💡 CloudPlatform.NONE 表示未检测到任何已知云平台(如本地 IDE 启动)。

18.5 底层原理

@ConditionalOnCloudPlatform 基于 @Conditional(OnCloudPlatformCondition.class) 实现。

Spring Boot 在启动时会通过 CloudPlatform.getActive(Environment) 方法检测当前平台,逻辑如下:

  1. 遍历所有已知 CloudPlatform 类型
  2. 调用其 isActive(Environment env) 方法(检查环境变量、文件等)
  3. 返回第一个匹配的平台;若无匹配,返回 NONE

该判断只在应用启动时执行一次,性能开销极小。

18.6 注意事项

  1. 检测是"尽力而为"(best-effort)
    不保证 100% 准确(例如 AWS 检测需访问元数据服务,可能因网络策略失败)。
  2. 不适用于所有云厂商
    如阿里云、腾讯云等未被内置支持。若需识别,可:
  • 自定义 Condition
  • 使用 @ConditionalOnExpression 检查特定环境变量
  1. 与 @Profile 互补
    可结合使用:
java 复制代码
   @Configuration
   @Profile("prod")
   @ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
   public class ProdK8sConfig { }
  1. Spring Boot 3.x 变化
    对云平台的支持持续增强,但核心机制不变。

18.7 总结

@ConditionalOnCloudPlatform 是 Spring Boot 云原生能力的重要组成部分,它让应用能够:

  • 自动适配不同云环境
  • 安全地加载平台特有配置
  • 避免在本地开发时因缺少云资源而启动失败

虽然在简单项目中可能用不到,但在混合云、多云部署或企业级 PaaS 迁移场景中,它是实现"一次构建,随处运行"的关键工具之一。

十九、@ConditionalOnSingleCandidate

@ConditionalOnSingleCandidate 是 Spring Boot 提供的一个条件注解(Conditional Annotation) ,用于根据 Spring 容器中是否存在且仅存在一个指定类型的 Bean 候选者(candidate) 来决定是否加载某个配置或 Bean。

19.1 核心作用

只有当 Spring 应用上下文中存在且仅存在一个指定类型的 Bean(即该类型是"单一候选")时,被注解的配置或方法才会生效。

这个注解常用于自动配置(Auto-Configuration) 中,确保在用户已经明确提供了一个 Bean 的情况下,才启用与之配套的辅助组件;如果用户没提供,或者提供了多个(导致歧义),则不启用,避免冲突。

19.2 基本用法

java 复制代码
@Bean
@ConditionalOnSingleCandidate(DataSource.class)
public MyDatabaseService myDatabaseService(DataSource dataSource) {
    return new MyDatabaseService(dataSource);
}

含义:

✅ 只有当容器中有且仅有一个 DataSource 类型的 Bean 时,才创建 MyDatabaseService

19.3 关键行为说明

场景 是否满足条件 说明
容器中没有 DataSource Bean ❌ 不满足 因为"不存在"
容器中有恰好一个 DataSource Bean ✅ 满足 符合"单一候选"
容器中有多个 DataSource Bean(如主从数据源) ❌ 不满足 存在歧义,不安全
容器中有多个 DataSource,但其中一个被 @Primary 标记 满足 Spring 认为 @Primary 的那个是"主要候选",视为单一

重要规则
@ConditionalOnSingleCandidate 会考虑 @Primary 注解

即:如果有多个候选,但存在一个 @Primary 的 Bean,则仍视为"单一候选"。

19.4 典型应用场景

✅ 场景 1:自动配置依赖主数据源的服务

Spring Boot 内置的很多自动配置都使用此注解,例如:

java 复制代码
// Spring Boot 自动配置片段
@Bean
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnMissingBean // 确保用户没自己定义
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}
  • 如果用户定义了唯一一个 DataSource → 自动配 JdbcTemplate
  • 如果用户没定义 DataSource → 不配(因为前提不满足)
  • 如果用户定义了两个 DataSource(如读写分离),但标了一个 @Primary → 仍会配 JdbcTemplate,并注入 @Primary 的那个
✅ 场景 2:避免多数据源下的歧义配置

假设你开发一个监控组件,只适用于"单一数据源"场景:

java 复制代码
@Configuration
public class DataSourceMonitorAutoConfiguration {

    @Bean
    @ConditionalOnSingleCandidate(DataSource.class)
    public DataSourceHealthIndicator healthIndicator(DataSource ds) {
        return new DataSourceHealthIndicator(ds);
    }
}

在多数据源环境下,用户需自行配置多个 HealthIndicator,而不是由自动配置"猜"该用哪个。

19.5 与类似注解对比

注解 判断逻辑
@ConditionalOnBean(DataSource.class) 只要存在至少一个 DataSource 就满足
@ConditionalOnMissingBean(DataSource.class) 不存在 DataSource 时满足
@ConditionalOnSingleCandidate(DataSource.class) 存在且唯一(或有 @Primary) 时满足

🔍 举例:

  • 用户定义了两个DataSource,无@Primary:
    • @ConditionalOnBean → ✅ 生效
    • @ConditionalOnSingleCandidate → ❌ 不生效

19.6 底层原理

@ConditionalOnSingleCandidate 基于 @Conditional(OnSingleCandidateCondition.class) 实现。

其判断逻辑(简化)如下:

  1. 调用 beanFactory.getBeanNamesForType(type) 获取所有候选 Bean 名称
  2. 如果数量为 0 → 不满足
  3. 如果数量为 1 → 满足
  4. 如果数量 > 1:
    • 检查是否有且仅有一个 Bean 被 @Primary 标记
    • 如果有 → 满足;否则 → 不满足

19.7 注意事项

  1. 仅适用于已注册到容器的 Bean
    它检查的是 当前 Spring ApplicationContext 中已定义的 Bean ,不包括尚未处理的 @Bean 方法。
  2. 常与 @ConditionalOnMissingBean 配合使用
    确保不会覆盖用户自定义的同类型 Bean:
java 复制代码
   @Bean
   @ConditionalOnSingleCandidate(DataSource.class)
   @ConditionalOnMissingBean(CustomService.class)
   public CustomService customService(DataSource ds) { ... }
  1. 不适用于泛型类型(需谨慎)
    List<String> 这类泛型,可能无法精确匹配,建议用具体类型。

19.8 总结

@ConditionalOnSingleCandidate 是 Spring Boot 自动配置体系中的"安全阀",它确保:

  • 明确、无歧义的上下文中启用配套功能
  • 避免在多实现(如多数据源、多缓存)场景下做出错误假设
  • 尊重用户的 @Primary 选择

它是构建健壮、可扩展、用户友好的 Starter 组件的关键工具之一。

二十、@ConfigurationProperties

@ConfigurationPropertiesSpring Boot 中用于将外部配置(如 application.properties 或 application.yml)绑定到 Java Bean 的核心注解。它提供了一种类型安全、结构清晰的方式来管理应用配置。

20.1 核心作用

自动将配置文件中的属性值映射到一个 Java 对象的字段上,实现"配置即对象"。

相比传统的 @Value("${xxx}") 逐个注入,@ConfigurationProperties 支持:

  • 批量绑定
  • 类型安全
  • 嵌套对象
  • 松散绑定(Relaxed Binding)
  • 配置校验(JSR-303/380)
  • 元数据生成(IDE 自动提示)

20.2 基本用法

1. 定义配置类
java 复制代码
@Component // 或通过 @EnableConfigurationProperties 注册
@ConfigurationProperties(prefix = "app.user")
public class UserProperties {
    private String name;
    private int age;
    private boolean active;

    // getter / setter 必须存在(用于绑定)
}
2. 配置文件(application.yml
yaml 复制代码
app:
  user:
    name: Alice
    age: 30
    active: true

application.properties

properties 复制代码
app.user.name=Alice
app.user.age=30
app.user.active=true
3. 使用配置
java 复制代码
@Service
public class UserService {
    private final UserProperties userProps;

    public UserService(UserProperties userProps) {
        this.userProps = userProps;
    }

    public void printInfo() {
        System.out.println(userProps.getName()); // 输出: Alice
    }
}

20.3 关键特性详解

✅ 1. 前缀绑定(prefix)
  • 所有以 prefix. 开头的属性会自动映射到该类字段。
  • 支持多级嵌套:prefix.sub.field
✅ 2. 松散绑定(Relaxed Binding)

Spring Boot 自动匹配多种命名风格,以下写法等效:

Java 字段 properties 写法 yml 写法
userName app.user.user-name app.user.userName app.user.user_name user-name: userName: user_name:

支持:驼峰、短横线(kebab-case)、下划线、大写等。

✅ 3. 嵌套对象支持
java 复制代码
@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private Database database = new Database();
    private Mail mail = new Mail();

    public static class Database {
        private String url;
        private String username;
        // getter/setter
    }

    public static class Mail {
        private String host;
        private int port;
        // getter/setter
    }
}

对应 application.yml

yaml 复制代码
app:
  database:
    url: jdbc:mysql://...
    username: root
  mail:
    host: smtp.example.com
    port: 587
✅ 4. 集合与 Map 绑定
java 复制代码
public class AppProperties {
    private List<String> servers = new ArrayList<>();
    private Map<String, String> metadata = new HashMap<>();
}
yaml 复制代码
app:
  servers:
    - server1
    - server2
  metadata:
    env: prod
    region: us-east-1
✅ 5. 配置校验(Validation)

添加 @Validated + JSR-303 注解:

java 复制代码
@Component
@ConfigurationProperties(prefix = "app.user")
@Validated
public class UserProperties {
    @NotBlank
    private String name;

    @Min(1)
    @Max(150)
    private int age;

    // getter/setter
}

启动时若配置不合法,会抛出 BindValidationException

✅ 6. 默认值支持

通过字段初始化或 @DefaultValue(需配合 @ConstructorBinding):

java 复制代码
private boolean enabled = true; // 默认为 true

或使用构造函数绑定(推荐不可变配置):

java 复制代码
@ConstructorBinding
@ConfigurationProperties(prefix = "app.retry")
public class RetryProperties {
    private final int maxAttempts;
    private final long delayMs;

    public RetryProperties(@DefaultValue("3") int maxAttempts,
                          @DefaultValue("1000") long delayMs) {
        this.maxAttempts = maxAttempts;
        this.delayMs = delayMs;
    }
}

⚠️ 使用 @ConstructorBinding 时,类必须是 final,且无 setter。

20.4 注册方式(两种)

方式式 1:直接加 @Component
java 复制代码
@Component
@ConfigurationProperties(prefix = "app.user")
public class UserProperties { ... }
方式 2:通过 @EnableConfigurationProperties(推荐用于自动配置)
java 复制代码
@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class AppConfig { }

在 Starter 或 AutoConfiguration 中常用方式 2,避免强制用户扫描该类。


20.5 生成配置元数据(IDE 提示)

添加依赖(Maven):

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

编译后生成 META-INF/spring-configuration-metadata.json,使 IDE(IntelliJ/Eclipse)能:

  • 自动提示配置项
  • 标记未知属性(警告)
  • 显示默认值和描述

可配合 @Description 使用:

java 复制代码
public class UserProperties {
    @Description("用户姓名,不能为空")
    private String name;
}

20.6 与 @Value 对比

特性 @ConfigurationProperties @Value
批量绑定 ✅ 支持整个对象 ❌ 只能单个属性
类型安全 ✅ 编译期检查 ❌ 字符串模板,易出错
松散绑定 ✅ 自动适配命名风格 ❌ 必须完全匹配
嵌套/集合 ✅ 原生支持 ❌ 需 SpEL,复杂
校验 ✅ 支持 JSR-303 ❌ 不支持
IDE 提示 ✅ 生成元数据 ❌ 无
使用场景 复杂配置、模块化配置 简单单个值

📌 官方建议

"Whenever you have multiple properties of the same type, strongly consider using @ConfigurationProperties instead of @Value."

20.7 常见问题

❓ 为什么 setter 必须存在?
  • Spring Boot 通过 JavaBean 规范(getter/setter)进行反射赋值。
  • 若使用 @ConstructorBinding,则通过构造函数注入,无需 setter。
❓ 配置没生效?
  • 检查是否注册了 Bean(加了 @Component@EnableConfigurationProperties
  • 检查 prefix 是否正确
  • 检查字段是否有 public setter

20.8 总结

@ConfigurationProperties 是 Spring Boot 配置管理的最佳实践,它让配置:

  • 结构化(对象代替字符串)
  • 类型安全(编译检查)
  • 可维护(集中管理)
  • 可验证(启动失败早发现)
  • 开发者友好(IDE 智能提示)

适用于所有中大型项目,是构建云原生、12-Factor 应用的关键工具之一。

相关推荐
ihgry1 分钟前
SpringCloudAlibaba
后端
悟空码字5 分钟前
SpringBoot + Redis分布式锁深度剖析,性能暴涨的秘密全在这里
java·spring boot·后端
奋进的芋圆6 分钟前
Spring Boot中实现定时任务
java·spring boot·后端
Jasmine_llq8 分钟前
《P3200 [HNOI2009] 有趣的数列》
java·前端·算法·线性筛法(欧拉筛)·快速幂算法(二进制幂)·勒让德定理(质因子次数统计)·组合数的质因子分解取模法
sww_102611 分钟前
xxl-job原理分析
java
星环处相逢11 分钟前
K8s 实战笔记:3 种发布策略 + YAML 配置全攻略
java·docker·kubernetes
BD_Marathon13 分钟前
Spring——容器
java·后端·spring
武子康18 分钟前
大数据-206 用 NumPy 矩阵乘法手写多元线性回归:正规方程、SSE/MSE/RMSE 与 R²
大数据·后端·机器学习
LaLaLa_OvO19 分钟前
spring boot2.0 里的 javax.validation.Constraint 加入 service
java·数据库·spring boot
小王和八蛋20 分钟前
负载均衡之DNS轮询
后端·算法·程序员