Appearance
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
性能优化
- 条件判断要轻量级:避免在
matches
方法中执行耗时操作 - 合理使用缓存:对于复杂的条件判断,考虑缓存结果
- 明确的条件逻辑:确保条件判断逻辑清晰且可预测
WARNING
注意事项
- 避免循环依赖:条件判断不应依赖正在注册的 Bean
- 测试覆盖:为自定义条件类编写充分的单元测试
- 文档记录:为复杂的条件逻辑提供清晰的文档说明
总结
Condition
接口是 Spring 框架中实现条件化 Bean 注册的核心机制。通过合理使用条件接口,我们可以:
- 🎯 提高应用灵活性:根据不同环境和配置动态调整 Bean 注册
- 🚀 优化启动性能:避免注册不必要的 Bean
- 🔧 简化配置管理:通过条件注解实现配置的自动化
- 📦 支持模块化设计:基于条件实现功能模块的可插拔
掌握 Condition
接口的使用,是构建健壮、灵活的 Spring 应用程序的重要技能。