2-4 条件注解

2 阅读3分钟

2-4 条件注解

概念解析

@Conditional 家族

注解说明
@Conditional基础条件注解
@ConditionalOnClass类路径包含指定类时生效
@ConditionalOnMissingClass类路径不包含指定类时生效
@ConditionalOnBean容器中存在指定 Bean 时生效
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效
@ConditionalOnProperty配置属性满足条件时生效
@ConditionalOnResource资源文件存在时生效
@ConditionalOnWebApplication是 Web 应用时生效
@ConditionalOnNotWebApplication非 Web 应用时生效

条件组合

注解说明
@ConditionalOnBean + @ConditionalOnProperty组合条件

代码示例

1. 基础 @Conditional

// 自定义条件:只有 Linux 环境才创建
public class LinuxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment()
            .getProperty("os.name")
            .contains("Linux");
    }
}

// 使用自定义条件
@Configuration
public class ConditionalConfig {

    @Bean
    @Conditional(LinuxCondition.class)
    public Runnable linuxTask() {
        return () -> System.out.println("Running on Linux");
    }
}

2. 常用条件注解

@Configuration
public class AutoConfiguration {

    // 条件1:类路径中存在某个类
    @Bean
    @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }

    // 条件2:类路径中不存在某个类
    @Bean
    @ConditionalOnMissingClass("com.google.gson.Gson")
    public StringHttpMessageConverter stringConverter() {
        return new StringHttpMessageConverter();
    }

    // 条件3:容器中存在某个 Bean
    @Bean
    @ConditionalOnBean(DataSource.class)
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    // 条件4:容器中不存在某个 Bean
    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public SimpleCacheManager simpleCacheManager() {
        return new SimpleCacheManager();
    }

    // 条件5:配置属性满足条件
    @Bean
    @ConditionalOnProperty(
        prefix = "app.cache",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true  // 缺失时也算 true
    )
    public CacheService cacheService() {
        return new RedisCacheService();
    }

    // 条件6:是 Web 应用
    @Bean
    @ConditionalOnWebApplication(type = Type.SERVLET)
    public WebInterceptor webInterceptor() {
        return new WebInterceptor();
    }
}

3. 条件注解组合

@Configuration
@ConditionalOnClass(RedisOperations.class)
@ConditionalOnProperty(
    prefix = "spring.data.redis",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = false
)
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }
}

4. 自定义条件注解

// 定义组合注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(DatabaseTypeCondition.class)
@Documented
public @interface ConditionalOnDatabase {
    DatabaseType value();

    enum DatabaseType {
        MYSQL, POSTGRESQL, ORACLE, SQLSERVER
    }
}

// 实现条件逻辑
public class DatabaseTypeCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata
            .getAnnotationAttributes(ConditionalOnDatabase.class.getName());

        DatabaseType requiredType = (DatabaseType) attributes.get("value");

        String url = context.getEnvironment()
            .getProperty("spring.datasource.url");

        if (requiredType == DatabaseType.MYSQL) {
            return url != null && url.contains("mysql");
        }

        return false;
    }
}

// 使用
@Configuration
@ConditionalOnDatabase(DatabaseType.MYSQL)
public class MySqlConfig {
    @Bean
    public MySqlService mySqlService() {
        return new MySqlService();
    }
}

5. Spring Boot 自动配置中的条件

// Spring Boot 内部大量使用条件注解
@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyBatisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(SqlSessionFactory.class)
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
        // ...
    }

    @Bean
    @ConditionalOnProperty(
        name = "mybatis.mapper-locations",
        matchIfMissing = true
    )
    public MapperScannerConfigurer mapperScannerConfigurer() {
        // ...
    }
}

源码解读

@Conditional 解析流程

@Configuration 类加载
        ↓
ConfigurationClassParser.parse()
        ↓
处理 @Conditional 注解
        ↓
ConditionEvaluator.shouldSkip()
        ↓
遍历所有 Condition.matches()
        ↓
全部返回 true → 配置类生效
任一返回 false → 配置类跳过

Condition 接口实现

public interface Condition {
    // context: 条件上下文(Environment、BeanFactory 等)
    // metadata: 注解元数据
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

public class ConditionContextImpl implements ConditionContext {
    private final Environment environment;
    private final BeanDefinitionRegistry registry;
    private final ResourceLoader resourceLoader;
    private final ClassLoader classLoader;

    // 提供各种获取信息的方法
}

常见坑点

⚠️ 坑 1:条件顺序导致不生效

问题:条件 Bean 之间有依赖但顺序不对

// ❌ orderService 可能还不存在
@Bean
@ConditionalOnBean(OrderService.class)  // 可能找不到
public OrderController orderController() {
    return new OrderController(orderService);
}

// ✅ 应该声明依赖关系
@Configuration
@AutoConfigureAfter(OtherAutoConfiguration.class)
public class MyAutoConfiguration { }

⚠️ 坑 2:@ConditionalOnProperty 配置错误

// ❌ 常见错误:前缀和属性名写错
@ConditionalOnProperty(prefix = "app", name = "cache.enable")  // 实际是 cache.enabled

// ✅ 检查配置文件中的实际名称
// application.yml 中:
// app:
//   cache:
//     enabled: true

@ConditionalOnProperty(
    prefix = "app.cache",
    name = "enabled",
    havingValue = "true"
)

⚠️ 坑 3:条件类的类加载问题

问题:ClassNotFoundException

// ⚠️ 直接使用类可能触发加载
@ConditionalOnClass(Gson.class)
public class GsonConfig {
    // 如果 Gson 不在 classpath,配置类加载时就报错
}

// ✅ 使用类名字符串
@ConditionalOnClass(name = "com.google.gson.Gson")
public class GsonConfig { }

面试题

Q1:@Conditional 的执行顺序?

参考答案

  1. 解析顺序:按注解在类上的声明顺序执行
  2. 短路机制:遇到第一个返回 false 的条件立即跳过
  3. 设计原则:更精确的条件放前面
@Configuration
@Conditional({ConditionA.class, ConditionB.class})
public class MyConfig {
    // 先检查 A,再检查 B
    // A && B 都为 true 才生效
}

Q2:Spring Boot 如何实现自动配置的条件化?

参考答案

Spring Boot 的自动配置大量使用 @ConditionalOn* 系列注解:

// 典型的自动配置类
@Configuration
@ConditionalOnClass(UserService.class)      // 类存在
@ConditionalOnProperty(prefix = "app",       // 配置存在
    name = "user.enabled", havingValue = "true")
@ConditionalOnMissingBean(UserService.class) // Bean 不存在
@EnableConfigurationProperties(UserProperties.class)
public class UserAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public UserService userService() {
        return new UserServiceImpl();
    }
}

Q3:如何排查条件不生效?

参考答案

  1. 启用自动配置报告

    debug: true
    
  2. 查看日志:搜索 "CONDITIONS EVALUATION REPORT"

  3. 常用原因

    • @ConditionalOnBean 找不到依赖的 Bean
    • @ConditionalOnClass 类不存在
    • @ConditionalOnProperty 配置值不匹配
    • @ConditionalOnMissingBean 已存在该 Bean
  4. 强制包含

    @SpringBootApplication(exclude = {UserAutoConfiguration.class})