Skip to content

Condition 条件接口

Condition 是 Spring Framework 提供的一个函数式接口,用于在 Bean 注册过程中进行条件判断。它允许开发者根据运行时环境、配置信息或其他条件来决定是否注册特定的 Bean。

接口概述

INFO

Condition 接口是 Spring 4.0 引入的功能,它为条件化的 Bean 注册提供了强大的支持。该接口使用 @FunctionalInterface 注解,可以作为 Lambda 表达式或方法引用的目标。

基本定义

kotlin
@FunctionalInterface
interface Condition {
    fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean
}

核心方法

matches 方法

matches 方法是 Condition 接口的唯一方法,用于判断条件是否匹配:

kotlin
fun matches(
    context: ConditionContext,     // 条件上下文
    metadata: AnnotatedTypeMetadata // 被检查的类或方法的元数据
): Boolean                         // 返回 true 表示条件匹配,false 表示拒绝注册

参数说明:

  • context: 提供环境信息、Bean 工厂、类加载器等上下文信息
  • metadata: 提供被检查的类或方法的注解元数据

与 @Conditional 注解配合使用

Condition 接口通常与 @Conditional 注解一起使用:

实际业务场景应用

1. 基于环境的配置

创建一个根据环境变量决定是否启用的数据源:

kotlin
// 自定义条件类
class DatabaseCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        val environment = context.environment
        // 检查是否启用数据库功能
        return environment.getProperty("app.database.enabled", Boolean::class.java, false)
    }
}

// 配置类
@Configuration
class DatabaseConfiguration {

    @Bean
    @Conditional(DatabaseCondition::class)
    fun dataSource(): DataSource {
        return HikariDataSource().apply {
            jdbcUrl = "jdbc:mysql://localhost:3306/myapp"
            username = "root"
            password = "password"
        }
    }

    @Bean
    @Conditional(DatabaseCondition::class)
    fun userService(dataSource: DataSource): UserService {
        return DatabaseUserService(dataSource)
    }
}

2. 基于系统属性的条件判断

kotlin
class SystemPropertyCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        // 检查操作系统类型
        val osName = System.getProperty("os.name").lowercase()
        return osName.contains("windows")
    }
}

@Configuration
class PlatformSpecificConfiguration {

    @Bean
    @Conditional(SystemPropertyCondition::class)
    fun windowsFileService(): FileService {
        return WindowsFileService()
    }

    @Bean
    @ConditionalOnMissingBean(FileService::class)
    fun defaultFileService(): FileService {
        return UnixFileService()
    }
}

3. 基于类路径的条件判断

kotlin
class LibraryPresentCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        val classLoader = context.classLoader
        return try {
            // 检查特定库是否存在
            classLoader?.loadClass("com.fasterxml.jackson.databind.ObjectMapper")
            true
        } catch (e: ClassNotFoundException) {
            false
        }
    }
}

@Configuration
class JsonConfiguration {

    @Bean
    @Conditional(LibraryPresentCondition::class)
    fun jacksonObjectMapper(): ObjectMapper {
        return ObjectMapper().apply {
            configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        }
    }
}

Spring Boot 内置条件注解

Spring Boot 提供了许多基于 Condition 接口的内置条件注解:

常用条件注解对比

注解功能描述使用场景
@ConditionalOnProperty基于配置属性根据配置文件启用功能
@ConditionalOnClass基于类存在性检查依赖库是否存在
@ConditionalOnMissingBean基于 Bean 缺失提供默认实现
@ConditionalOnProfile基于激活的 Profile环境相关配置
@ConditionalOnExpression基于 SpEL 表达式复杂条件判断

实际应用示例

kotlin
@Configuration
class CacheConfiguration {

    // 只有在配置了 Redis 时才启用
    @Bean
    @ConditionalOnProperty(name = ["cache.type"], havingValue = "redis")
    fun redisTemplate(): RedisTemplate<String, Any> {
        return RedisTemplate<String, Any>().apply {
            // Redis 配置
        }
    }

    // 当 Redis 相关类存在时启用
    @Bean
    @ConditionalOnClass(RedisTemplate::class)
    fun redisCacheManager(): CacheManager {
        return RedisCacheManager.create(connectionFactory)
    }

    // 默认缓存实现(当没有其他缓存管理器时)
    @Bean
    @ConditionalOnMissingBean(CacheManager::class)
    fun simpleCacheManager(): CacheManager {
        return SimpleCacheManager()
    }
}

高级特性

1. ConfigurationCondition 接口

对于更精细的控制,可以实现 ConfigurationCondition 接口:

kotlin
class AdvancedCondition : ConfigurationCondition {

    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        // 条件判断逻辑
        return true
    }

    override fun getConfigurationPhase(): ConfigurationCondition.ConfigurationPhase {
        // 指定条件检查的阶段
        return ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN
    }
}

2. 多条件组合

可以在同一个 Bean 上应用多个条件:

kotlin
@Configuration
class MultiConditionConfiguration {

    @Bean
    @Conditional(DatabaseCondition::class, SystemPropertyCondition::class)
    fun complexService(): ComplexService {
        return ComplexService()
    }
}

NOTE

当应用多个条件时,所有条件都必须返回 true 才会注册 Bean(逻辑 AND 关系)。

3. 条件执行顺序

可以使用 @Order 注解控制条件的执行顺序:

kotlin
@Order(1)
class FirstCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        println("第一个条件检查")
        return true
    }
}

@Order(2)
class SecondCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        println("第二个条件检查")
        return true
    }
}

实战案例:动态 API 配置

kotlin
// 检查API版本的条件
class ApiVersionCondition : Condition {
    override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
        val apiVersion = context.environment.getProperty("api.version")

        // 从注解中获取期望的版本
        val expectedVersion = metadata.getAnnotationAttributes("ApiVersion")
            ?.get("value") as? String

        return apiVersion == expectedVersion
    }
}

// 自定义注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Conditional(ApiVersionCondition::class)
annotation class ApiVersion(val value: String)

// 使用示例
@RestController
class UserController {

    @GetMapping("/users")
    @ApiVersion("v1")
    fun getUsersV1(): List<UserV1> {
        return userServiceV1.getAllUsers()
    }

    @GetMapping("/users")
    @ApiVersion("v2")
    fun getUsersV2(): List<UserV2> {
        return userServiceV2.getAllUsers()
    }
}

@Configuration
class ApiConfiguration {

    @Bean
    @ApiVersion("v1")
    fun userServiceV1(): UserService {
        return UserServiceV1()
    }

    @Bean
    @ApiVersion("v2")
    fun userServiceV2(): UserService {
        return UserServiceV2()
    }
}

调试和测试

1. 条件评估日志

启用条件评估的调试日志:

kotlin
# application.yml
logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG

2. 单元测试条件类

kotlin
@ExtendWith(MockitoExtension::class)
class DatabaseConditionTest {

    @Mock
    private lateinit var context: ConditionContext

    @Mock
    private lateinit var environment: Environment

    @Mock
    private lateinit var metadata: AnnotatedTypeMetadata

    @Test
    fun `should return true when database is enabled`() {
        // Given
        whenever(context.environment).thenReturn(environment)
        whenever(environment.getProperty("app.database.enabled", Boolean::class.java, false))
            .thenReturn(true)

        val condition = DatabaseCondition()

        // When
        val result = condition.matches(context, metadata)

        // Then
        assertThat(result).isTrue()
    }

    @Test
    fun `should return false when database is disabled`() {
        // Given
        whenever(context.environment).thenReturn(environment)
        whenever(environment.getProperty("app.database.enabled", Boolean::class.java, false))
            .thenReturn(false)

        val condition = DatabaseCondition()

        // When
        val result = condition.matches(context, metadata)

        // Then
        assertThat(result).isFalse()
    }
}

最佳实践

TIP

性能优化

  1. 条件判断要轻量级:避免在 matches 方法中执行耗时操作
  2. 合理使用缓存:对于复杂的条件判断,考虑缓存结果
  3. 明确的条件逻辑:确保条件判断逻辑清晰且可预测

WARNING

注意事项

  1. 避免循环依赖:条件判断不应依赖正在注册的 Bean
  2. 测试覆盖:为自定义条件类编写充分的单元测试
  3. 文档记录:为复杂的条件逻辑提供清晰的文档说明

总结

Condition 接口是 Spring 框架中实现条件化 Bean 注册的核心机制。通过合理使用条件接口,我们可以:

  • 🎯 提高应用灵活性:根据不同环境和配置动态调整 Bean 注册
  • 🚀 优化启动性能:避免注册不必要的 Bean
  • 🔧 简化配置管理:通过条件注解实现配置的自动化
  • 📦 支持模块化设计:基于条件实现功能模块的可插拔

掌握 Condition 接口的使用,是构建健壮、灵活的 Spring 应用程序的重要技能。