Java注解@Conditional

@Conditional是 Spring Boot 自动配置的"大脑",它决定了何时、何条件下创建 Bean。让我用最通俗的方式给你讲清楚。

一句话理解 @Conditional

@Conditional 是 Spring 的"智能开关",它根据条件决定是否创建 Bean。

好比智能家居:

  • 条件:如果(有人在家 && 温度>30度)→ 打开空调
  • 条件:如果(没人在家)→ 关闭所有电器

Spring Boot 就是根据各种条件来决定:要不要创建 DataSource?要不要配置 Redis?要不要开启安全验证?

从简单到复杂

传统 Spring(手动配置)

@Configuration
public class AppConfig {
    
    // 不管需不需要,都会创建这个Bean
    @Bean
    public DataSource dataSource() {
        return new DataSource();
    }
}

Spring Boot(智能配置)

@Configuration
public class DataSourceAutoConfiguration {
    
    // 只有当类路径有DataSource类,并且配置了数据库连接时,才创建Bean
    @Bean
    @ConditionalOnClass(DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.url")
    public DataSource dataSource() {
        return new DataSource();
    }
}

@Conditional 家族

Spring Boot 提供了多种 @Conditional注解,每个都有特定用途:

注解 作用 示例场景
@ConditionalOnClass 类路径存在指定的类 有 MySQL 驱动才配置 DataSource
@ConditionalOnMissingClass 类路径不存在指定的类 没有 MongoDB 驱动才用默认配置
@ConditionalOnBean Spring 容器中存在指定的 Bean 已有 DataSource 就不重复创建
@ConditionalOnMissingBean Spring 容器中不存在指定的 Bean 没有 DataSource 时才创建
@ConditionalOnProperty 配置文件中有指定的属性 配置了 spring.redis.host才配置 Redis
@ConditionalOnExpression SpEL 表达式为 true 复杂的条件判断
@ConditionalOnJava 指定的 Java 版本 Java 8+ 才启用某些特性
@ConditionalOnWebApplication 是 Web 应用 Web 环境才配置 WebMvc
@ConditionalOnNotWebApplication 不是 Web 应用 非 Web 环境配置不同
@ConditionalOnResource 存在指定的资源文件 schema.sql才初始化数据库
@ConditionalOnJndi 存在指定的 JNDI JNDI 环境才使用
@ConditionalOnCloudPlatform 指定的云平台 在 Kubernetes 中才启用某些特性

核心注解详解

1. @ConditionalOnClass - “有才用”

@Configuration
@ConditionalOnClass({  // 必须同时存在这些类
    DataSource.class,
    JdbcTemplate.class,
    PlatformTransactionManager.class
})
public class DataSourceAutoConfiguration {
    // 只有当类路径有数据库相关类时,才配置数据源
}

实际应用:Spring Boot 根据你引入的 starter 自动判断

<!-- 引入 MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot 会自动配置 DataSource -->

2. @ConditionalOnMissingBean - “没有才创”

@Configuration
public class CacheConfiguration {
    
    @Bean
    @ConditionalOnMissingBean  // 如果容器中没有 CacheManager,才创建这个
    public CacheManager cacheManager() {
        return new SimpleCacheManager();
    }
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")  // 没有 redisTemplate 才创建
    public RedisTemplate<String, Object> redisTemplate() {
        return new RedisTemplate<>();
    }
}

使用场景:允许用户自定义 Bean,覆盖默认配置。

3. @ConditionalOnProperty - “配置了才用”

@Configuration
@ConditionalOnProperty(
    prefix = "spring.datasource",  // 配置前缀
    name = "url",                  // 属性名
    havingValue = "",              // 期望的值(默认只要存在就为true)
    matchIfMissing = false         // 属性缺失时是否匹配
)
public class DataSourceAutoConfiguration {
    // 只有当配置了 spring.datasource.url 时才生效
}

// 更常用的写法
@ConditionalOnProperty("spring.datasource.url")  // 只要配置了这个属性就生效
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")  // 必须为true
@ConditionalOnProperty(name = "cache.type", havingValue = "redis", matchIfMissing = true)  // 默认使用redis

4. @ConditionalOnExpression - “复杂条件”

@Configuration
@ConditionalOnExpression(
    "#{environment['spring.datasource.url'] != null && " +
    "environment['spring.datasource.username'] != null}"
)
public class DataSourceAutoConfiguration {
    // 只有同时配置了url和username才生效
}

// 或者更复杂的条件
@ConditionalOnExpression(
    "'${spring.profiles.active}'.equals('prod') && " +
    "${server.port} > 8080"
)

实战案例

案例1:多数据源配置

@Configuration
public class DataSourceConfig {
    
    // 主数据源 - 默认配置
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    @ConditionalOnProperty(prefix = "spring.datasource.primary", name = "url")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    // 从数据源 - 可选配置
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    @ConditionalOnProperty(prefix = "spring.datasource.secondary", name = "url")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    // 内存数据库 - 开发环境使用
    @Bean
    @ConditionalOnMissingBean(DataSource.class)  // 没有其他数据源时才创建
    @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
    public DataSource h2DataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .build();
    }
}

案例2:多缓存策略

@Configuration
public class CacheConfig {
    
    // 默认使用本地缓存
    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    @ConditionalOnProperty(name = "cache.type", havingValue = "local", matchIfMissing = true)
    public CacheManager localCacheManager() {
        return new ConcurrentMapCacheManager("users", "products");
    }
    
    // 如果配置了Redis,使用Redis缓存
    @Bean
    @ConditionalOnClass(RedisConnectionFactory.class)
    @ConditionalOnProperty(name = "cache.type", havingValue = "redis")
    public CacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager cacheManager = RedisCacheManager.create(connectionFactory);
        cacheManager.setCacheNames(Arrays.asList("users", "products"));
        return cacheManager;
    }
    
    // 如果配置了Caffeine,使用Caffeine缓存
    @Bean
    @ConditionalOnClass(Caffeine.class)
    @ConditionalOnProperty(name = "cache.type", havingValue = "caffeine")
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(1000));
        return cacheManager;
    }
}

案例3:不同环境的Bean

@Configuration
public class EmailConfig {
    
    // 开发环境:使用 Mock 邮件服务
    @Bean
    @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
    @Primary  // 优先使用这个Bean
    public EmailService mockEmailService() {
        return new MockEmailService();  // 只打印日志,不真实发送
    }
    
    // 测试环境:使用测试邮件服务
    @Bean
    @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "test")
    @Primary
    public EmailService testEmailService() {
        return new TestEmailService();  // 发送到测试邮箱
    }
    
    // 生产环境:使用真实邮件服务
    @Bean
    @ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
    @Primary
    public EmailService prodEmailService() {
        return new SmtpEmailService();  // 真实发送邮件
    }
    
    // 如果没有指定环境,默认用Mock(安全)
    @Bean
    @ConditionalOnMissingBean(EmailService.class)
    public EmailService defaultEmailService() {
        return new MockEmailService();
    }
}

案例4:功能开关

@Configuration
public class FeatureConfig {
    
    // 是否开启验证码功能
    @Bean
    @ConditionalOnProperty(name = "feature.captcha.enabled", havingValue = "true")
    public CaptchaService captchaService() {
        return new GoogleCaptchaService();
    }
    
    // 是否开启短信功能
    @Bean
    @ConditionalOnProperty(name = "feature.sms.enabled", havingValue = "true")
    @ConditionalOnClass(name = "com.aliyun.dysmsapi20170525.Client")  // 有阿里云SDK才创建
    public SmsService smsService() {
        return new AliyunSmsService();
    }
    
    // 是否开启支付功能
    @Bean
    @ConditionalOnExpression(
        "#{environment['feature.payment.enabled'] == 'true' && " +
        "environment['payment.alipay.app-id'] != null && " +
        "environment['payment.alipay.private-key'] != null}"
    )
    public PaymentService paymentService() {
        return new AlipayService();
    }
}

案例5:自定义条件注解

// 1. 定义条件类
public class OnWindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os = context.getEnvironment().getProperty("os.name");
        return os != null && os.toLowerCase().contains("win");
    }
}

// 2. 创建自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnWindowsCondition.class)
public @interface ConditionalOnWindows {
}

// 3. 使用自定义注解
@Configuration
public class OsSpecificConfig {
    
    @Bean
    @ConditionalOnWindows
    public FileService windowsFileService() {
        return new WindowsFileService();  // Windows专用实现
    }
    
    @Bean
    @ConditionalOnLinux  // 假设有类似的Linux条件
    public FileService linuxFileService() {
        return new LinuxFileService();  // Linux专用实现
    }
}

源码解析

Spring Boot 自动配置的魔法

打开 spring-boot-autoconfigure包的源码:

// DataSourceAutoConfiguration.java 片段
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, 
          DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)  // 内嵌数据库条件
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration {
    }

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)  // 连接池数据源条件
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class,  // 自动选择连接池
              DataSourceConfiguration.Tomcat.class,
              DataSourceConfiguration.Dbcp2.class,
              DataSourceConfiguration.Generic.class,
              DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration {
    }
}

条件注解的执行时机

// 简化版的执行逻辑
public boolean shouldCreateBean() {
    // 1. 检查 @ConditionalOnClass
    if (!classExists("com.mysql.cj.jdbc.Driver")) {
        return false;  // 没有MySQL驱动,不创建DataSource
    }
    
    // 2. 检查 @ConditionalOnProperty
    if (!propertyExists("spring.datasource.url")) {
        return false;  // 没有配置数据库URL,不创建DataSource
    }
    
    // 3. 检查 @ConditionalOnMissingBean
    if (beanExists("dataSource")) {
        return false;  // 已经存在dataSource Bean,不重复创建
    }
    
    // 4. 检查 @ConditionalOnWebApplication
    if (!isWebApplication()) {
        return false;  // 不是Web应用,不创建某些Web相关的Bean
    }
    
    return true;  // 所有条件都满足,创建Bean
}

条件注解的组合使用

组合条件

@Configuration
// 组合多个条件:必须是Web应用,且配置了Redis,且没有自定义的RedisTemplate
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
@ConditionalOnMissingBean(RedisTemplate.class)
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

条件优先级

@Configuration
public class MultiConditionConfig {
    
    // 条件1:开发环境使用H2
    @Bean
    @Profile("dev")  // 这是另一种条件注解
    @ConditionalOnClass(name = "org.h2.Driver")
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
    
    // 条件2:生产环境使用MySQL
    @Bean
    @Profile("prod")
    @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
    @ConditionalOnProperty("spring.datasource.url")
    public DataSource prodDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    // 条件3:如果以上都不满足,使用默认内存数据库
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource defaultDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .generateUniqueName(true)
            .build();
    }
}

调试技巧

1. 查看哪些条件生效了

# application.yml
debug: true  # 开启调试模式

启动时会输出:

============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:  # 匹配的条件
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource', 
        'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' 
        (OnClassCondition)
        
Negative matches:  # 未匹配的条件
-----------------
   KafkaAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 
           'org.apache.kafka.clients.consumer.Consumer' (OnClassCondition)

2. 自定义条件日志

@Component
public class ConditionLogger implements ApplicationListener<ApplicationPreparedEvent> {
    
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
        
        // 打印所有条件判断
        System.out.println("=== 条件判断报告 ===");
        System.out.println("是否Web应用: " + 
            WebApplicationTypeUtils.getWebApplicationType(event.getApplicationContext()));
        System.out.println("类路径是否有DataSource: " + 
            ClassUtils.isPresent("javax.sql.DataSource", getClass().getClassLoader()));
        System.out.println("配置了数据库URL: " + 
            env.containsProperty("spring.datasource.url"));
    }
}

性能优化

1. 避免不必要的条件检查

// 不好:每次都要检查
@ConditionalOnExpression(
    "T(org.springframework.util.StringUtils).hasText('${some.property}')"
)

// 好:使用 @ConditionalOnProperty,Spring会缓存结果
@ConditionalOnProperty("some.property")

2. 合理使用 matchIfMissing

@Configuration
// matchIfMissing = true 表示属性缺失时也匹配(默认值)
@ConditionalOnProperty(
    prefix = "app.feature",
    name = "advanced",
    havingValue = "true",
    matchIfMissing = false  // 明确指定,提高可读性
)
public class AdvancedFeatureConfig {
    // 只有当 app.feature.advanced=true 时才生效
}

3. 条件分组

@Configuration
// 把相关条件放在一起,便于理解和维护
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@ConditionalOnProperty(prefix = "spring.mvc", name = "enabled", matchIfMissing = true)
public class WebMvcAutoConfiguration {
    // Web MVC 自动配置
}

实际应用场景

场景1:多环境配置

# application-dev.yml
feature:
  cache: true
  mq: false  # 开发环境关闭消息队列
  monitor: false

# application-prod.yml  
feature:
  cache: true
  mq: true   # 生产环境开启消息队列
  monitor: true
@Configuration
public class FeatureToggleConfig {
    
    @Bean
    @ConditionalOnProperty(name = "feature.cache", havingValue = "true")
    public CacheService cacheService() {
        return new RedisCacheService();
    }
    
    @Bean
    @ConditionalOnProperty(name = "feature.mq", havingValue = "true")
    @ConditionalOnClass(RabbitTemplate.class)
    public MessageQueueService mqService() {
        return new RabbitMQService();
    }
    
    @Bean
    @ConditionalOnProperty(name = "feature.monitor", havingValue = "true")
    public MonitorService monitorService() {
        return new PrometheusMonitorService();
    }
}

场景2:模块化配置

// 用户模块 - 只在有用户相关配置时启用
@Configuration
@ConditionalOnProperty(prefix = "module.user", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(UserProperties.class)
public class UserModuleAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public UserService userService(UserProperties properties) {
        return new UserServiceImpl(properties);
    }
}

// 订单模块 - 只在有订单相关配置时启用
@Configuration
@ConditionalOnProperty(prefix = "module.order", name = "enabled", havingValue = "true")
@ConditionalOnClass(OrderService.class)
public class OrderModuleAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public OrderService orderService() {
        return new OrderServiceImpl();
    }
}

场景3:兼容性配置

@Configuration
public class CompatibilityConfig {
    
    // Java 8 特性
    @Bean
    @ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.EIGHT)
    public Feature java8Feature() {
        return new Java8Feature();
    }
    
    // Java 11+ 特性
    @Bean
    @ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.ELEVEN)
    public Feature java11Feature() {
        return new Java11Feature();
    }
    
    // 旧版兼容
    @Bean
    @ConditionalOnClass(name = "org.hibernate.SessionFactory")
    @ConditionalOnMissingBean(name = "entityManagerFactory")
    public SessionFactory sessionFactory() {
        return new LegacySessionFactory();
    }
}

最佳实践

  1. 优先使用特定条件注解

    // 好:明确
    @ConditionalOnProperty("app.feature.enabled")
    
    // 不好:模糊
    @ConditionalOnExpression("#{environment['app.feature.enabled'] == 'true'}")
    
  2. 条件组合要合理

    // 明确的优先级
    @Profile("prod")  // 1. 环境
    @ConditionalOnClass(DataSource.class)  // 2. 类路径
    @ConditionalOnProperty("db.url")  // 3. 配置
    @ConditionalOnMissingBean  // 4. 容器状态
    
  3. 提供合理的默认值

    @Bean
    @ConditionalOnMissingBean  // 允许用户自定义覆盖
    public CacheService cacheService() {
        return new DefaultCacheService();
    }
    
  4. 条件要可测试

    @Test
    @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
    void test64BitOnly() {
        // 只在64位系统运行的测试
    }
    

总结@Conditional是 Spring Boot 自动配置的基石,它让 Spring Boot 能够:

  1. 智能判断:根据环境、配置、类路径自动决策
  2. 避免冲突:防止重复创建 Bean
  3. 灵活配置:支持多种条件组合
  4. 便于扩展:支持自定义条件注解

记住核心思想“条件满足才创建,不满足就不创建”,这就是 Spring Boot 自动配置的智慧所在。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐