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

    • 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 核心注解速查表

@ConditionalOnProperty 注解

一句话定位:@ConditionalOnProperty 是 Spring Boot 自动配置的配置属性开关。它让 Bean 的注册与否完全受控于 application.yml 或 application.properties 中的某个键值对,实现"一键启停"功能模块的能力。


定义与作用

@ConditionalOnProperty 根据 Spring Environment 中的配置属性值 决定是否注册 Bean 或加载配置类。它是将"业务开关"从代码硬编码中解耦出来的标准手段。

与 @ConditionalOnClass 不同,@ConditionalOnClass 判断的是"有没有依赖"(静态条件),而 @ConditionalOnProperty 判断的是"用户是否显式开启"(动态条件)。二者经常组合使用:先检查类路径有依赖,再检查用户是否启用了该功能。

典型应用场景

场景配置示例效果
功能开关feixiang.student.cache.enabled=true开启 Redis 缓存
多实现切换feixiang.student.storage.type=oss启用 OSS 存储,禁用本地存储
环境特性spring.profiles.active=dev配合 @Profile 使用
安全特性feixiang.student.encryption.enabled=true开启请求加密拦截器

适用位置与常用属性

适用位置

@ConditionalOnProperty 可以标注在:

  1. 类级别:控制整个配置类是否生效
  2. @Bean 方法级别:控制单个 Bean 是否注册
// 类级别:整个功能模块受控
@Configuration
@ConditionalOnProperty(prefix = "feixiang.student.cache", name = "enabled", havingValue = "true")
public class CacheConfiguration { ... }

// 方法级别:单个 Bean 受控
@Configuration
public class StorageConfiguration {
    
    @Bean
    @ConditionalOnProperty(prefix = "feixiang.student.storage", name = "type", havingValue = "local")
    public StorageService localStorage() { ... }
    
    @Bean
    @ConditionalOnProperty(prefix = "feixiang.student.storage", name = "type", havingValue = "oss")
    public StorageService ossStorage() { ... }
}

常用属性

属性类型说明默认值
value / nameString[]要检查的配置属性名无
prefixString配置属性的前缀,与 name 拼接为完整键""
havingValueString期望的属性值,匹配时条件成立""
matchIfMissingboolean当配置属性不存在时,是否视为匹配false

属性组合规则:

  • 最终检查的配置键 = prefix + . + name
  • 如果 havingValue 未指定(默认 ""),则只要属性存在且不为 false 即匹配
  • 如果 matchIfMissing = true,则属性不存在时也视为匹配(常用于默认开启的功能)

核心原理

属性值匹配流程

与 @ConditionalOnClass 的组合决策

设计哲学:@ConditionalOnClass 解决"能不能配"的问题,@ConditionalOnProperty 解决"用户想不想配"的问题。二者叠加,既避免了缺少依赖时的启动失败,又赋予了用户显式控制权。


完整示例

场景简述

飞翔科技公司的学生成绩管理系统需要支持多种文件存储方式:本地磁盘存储和阿里云 OSS 存储。架构师白歌要求:

  1. 通过 application.yml 中的一个配置项切换存储实现
  2. 默认使用本地存储(不配置时也生效)
  3. 切换到 OSS 时,必须同时检查 OSS 相关依赖是否在类路径中

小崔需要设计一个基于 @ConditionalOnProperty 的存储策略自动切换方案。

操作前:硬编码实现导致切换困难

// 操作前:错误示范,实现类选择硬编码
package com.feixiang.student.service;

import org.springframework.stereotype.Service;

@Service
public class FileService {
    
    private final StorageService storage;
    
    public FileService() {
        // 硬编码!每次切换都要改代码、重新编译
        this.storage = new LocalStorageService();
        // this.storage = new OssStorageService();  // 想换 OSS?改代码!
    }
    
    public String upload(byte[] fileData, String filename) {
        return storage.store(fileData, filename);
    }
}

痛点:

  • 存储实现切换需要修改源码并重新打包
  • 无法在不同环境(开发用本地、生产用 OSS)使用不同实现
  • 如果 OSS 依赖未引入,直接 new OssStorageService() 会编译失败

使用该注解的完整代码

小崔首先定义存储接口和两种实现:

package com.feixiang.student.service;

public interface StorageService {
    String store(byte[] fileData, String filename);
}
package com.feixiang.student.service;

import org.springframework.stereotype.Component;

@Component
public class LocalStorageService implements StorageService {
    @Override
    public String store(byte[] fileData, String filename) {
        // 模拟保存到本地磁盘
        return "/data/feixiang/upload/" + filename;
    }
}

然后创建条件化的配置类:

package com.feixiang.student.config;

import com.feixiang.student.service.LocalStorageService;
import com.feixiang.student.service.OssStorageService;
import com.feixiang.student.service.StorageService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * 存储策略自动配置
 * 根据 feixiang.student.storage.type 配置切换实现
 */
@Configuration(proxyBeanMethods = false)
public class StorageAutoConfiguration {
    
    /**
     * 本地存储:默认实现
     * matchIfMissing = true 表示当配置不存在时,默认启用本地存储
     */
    @Bean
    @Primary
    @ConditionalOnProperty(
        prefix = "feixiang.student.storage",
        name = "type",
        havingValue = "local",
        matchIfMissing = true  // 默认开启
    )
    public StorageService localStorageService() {
        return new LocalStorageService();
    }
    
    /**
     * OSS 存储:需要显式配置 type=oss,且类路径有 OSS SDK
     */
    @Bean
    @ConditionalOnClass(name = "com.aliyun.oss.OSSClient")  // 检查 OSS SDK 是否存在
    @ConditionalOnProperty(
        prefix = "feixiang.student.storage",
        name = "type",
        havingValue = "oss"
    )
    public StorageService ossStorageService() {
        return new OssStorageService();
    }
}

OSS 实现类(需引入 aliyun-sdk-oss 依赖):

package com.feixiang.student.service;

import com.aliyun.oss.OSSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class OssStorageService implements StorageService {
    
    @Value("${feixiang.student.storage.oss.endpoint}")
    private String endpoint;
    
    @Value("${feixiang.student.storage.oss.bucket}")
    private String bucket;
    
    @Override
    public String store(byte[] fileData, String filename) {
        // 模拟 OSS 上传
        return "https://" + bucket + "." + endpoint + "/" + filename;
    }
}

对应的 application.yml 配置:

# 场景 A:开发环境,使用本地存储(不写 storage.type 或写 local)
feixiang:
  student:
    storage:
      type: local  # 不写这一行也行,因为 matchIfMissing = true

---
# 场景 B:生产环境,使用 OSS(需放在 application-prod.yml 中)
feixiang:
  student:
    storage:
      type: oss
      oss:
        endpoint: oss-cn-guangzhou.aliyuncs.com
        bucket: feixiang-student-prod

业务代码中无感知注入:

package com.feixiang.student.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class FileService {
    
    private final StorageService storageService;
    
    @Autowired
    public FileService(StorageService storageService) {
        this.storageService = storageService;
    }
    
    public String uploadStudentAvatar(byte[] imageData, Long studentId) {
        String filename = "avatar_" + studentId + ".jpg";
        String url = storageService.store(imageData, filename);
        System.out.println("[飞翔科技] 学生头像已保存至: " + url);
        return url;
    }
}

操作后运行结果及分析

场景 A:开发环境(type = local 或未配置)

2024-05-20 10:30:15.123  INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration         : 
    localStorageService matched: 
    @ConditionalOnProperty (feixiang.student.storage.type=local, matchIfMissing=true)
2024-05-20 10:30:15.456  INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration         : 
    ossStorageService did not match: 
    @ConditionalOnProperty (feixiang.student.storage.type) did not find value 'oss'
[飞翔科技] 学生头像已保存至: /data/feixiang/upload/avatar_2024001.jpg

场景 B:生产环境(type = oss,且类路径有 aliyun-sdk-oss)

2024-05-20 10:35:22.789  INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration         : 
    ossStorageService matched: 
    @ConditionalOnClass found 'com.aliyun.oss.OSSClient'
    @ConditionalOnProperty (feixiang.student.storage.type=oss)
2024-05-20 10:35:22.901  INFO 12345 --- [main] c.f.s.c.StorageAutoConfiguration         : 
    localStorageService did not match: 
    @ConditionalOnProperty (feixiang.student.storage.type) found value 'oss', expected 'local'
[飞翔科技] 学生头像已保存至: https://feixiang-student-prod.oss-cn-guangzhou.aliyuncs.com/avatar_2024001.jpg

分析:

  1. 默认行为:开发环境未配置 storage.type 时,matchIfMissing = true 使本地存储默认生效。
  2. 显式切换:生产环境配置 type: oss 后,OSS 存储 Bean 被注册,本地存储 Bean 因 havingValue 不匹配而被跳过。
  3. 双重保险:OSS 配置同时受 @ConditionalOnClass 保护,即使配置写错为 type: oss,如果类路径没有阿里云 SDK,也不会尝试注册 OssStorageService,避免 ClassNotFoundException。

易错场景与面试考点

易错场景一:havingValue 与配置值类型不匹配

小崔在配置文件中写:

feixiang:
  student:
    cache:
      enabled: true  # YAML 布尔值

但条件注解中误用字符串比较:

// 错误示范:havingValue 大小写不匹配
@Bean
@ConditionalOnProperty(
    prefix = "feixiang.student.cache",
    name = "enabled",
    havingValue = "TRUE"  // ← 大写,与 YAML 的 true 不匹配
)
public CacheManager cacheManager() { ... }

后果:@ConditionalOnProperty 使用字符串比较,"TRUE" 不等于 "true",条件不匹配,cacheManager Bean 未被注册。小崔排查半天,发现是大小写问题。

正确做法:havingValue 必须与配置值的字符串形式完全一致。YAML 的布尔值 true 在 Environment 中转为字符串 "true",因此 havingValue = "true" 才能匹配。

易错场景二:matchIfMissing 的误用导致功能意外开启

// 错误示范:敏感功能默认开启
@Bean
@ConditionalOnProperty(
    prefix = "feixiang.student.security",
    name = "debug-mode",
    matchIfMissing = true  // ← 危险!用户没配置时默认开启调试模式
)
public DebugInterceptor debugInterceptor() {
    return new DebugInterceptor();  // 打印所有请求参数,包括密码!
}

后果:生产环境忘记配置 debug-mode=false 时,调试拦截器默认生效,可能导致敏感信息泄露。

正确做法:涉及安全、性能、成本的功能,应将 matchIfMissing 设为 false(默认),要求用户显式开启。只有无害的便利功能(如本地存储)才适合默认开启。

面试考点

Q:@ConditionalOnProperty 和 @Profile 有什么区别?

@Profile 基于 Spring 的 Environment.getActiveProfiles() 判断,用于区分开发、测试、生产等环境维度;@ConditionalOnProperty 基于任意配置属性判断,用于控制功能维度的开关。二者可以组合使用,例如 @Profile("prod") + @ConditionalOnProperty("feature.x.enabled")。

Q:havingValue 未指定时,什么情况下条件会匹配?

当 havingValue 为默认值 "" 时,只要配置属性存在且值不为 false(字符串比较),条件即匹配。这意味着 enabled: true、enabled: yes、enabled: 1 都能匹配,但 enabled: false 不会匹配。

Q:能否在一个类上同时使用多个 @ConditionalOnProperty?

不能直接重复使用同一注解,但可以通过组合条件实现。Spring Boot 提供了 @ConditionalOnExpression(基于 SpEL)和自定义 @Conditional 实现来处理多条件逻辑。或者将多个条件拆分到不同的 @Bean 方法上。

Q:@ConditionalOnProperty 支持检查 List 或 Map 类型的配置吗?

name 属性支持数组形式,例如 @ConditionalOnProperty(name = {"a", "b"}) 表示 a 和 b 都必须存在。但它不直接支持检查 YAML 中的 List 结构(如 feixiang.list[0])。对于复杂结构,应配合 @ConfigurationProperties 将配置绑定到 POJO,再在业务逻辑中判断。

上一页
@ConditionalOnMissingBean 注解