一、简介

在本教程中,我们将了解 @Conditional 注释。它用于根据定义的条件指示给定组件是否有资格注册。

我们将学习如何使用预定义的条件注释,将它们与不同的条件结合起来,并创建我们自己的自定义的、基于条件的注释。

2. 声明条件

在我们开始实现之前,让我们看看在哪些情况下可以使用条件注释。

最常见的用法是 包含或排除整个配置类

@Configuration
@Conditional(IsDevEnvCondition.class)
class DevEnvLoggingConfiguration {
    // ...
}

或者只是一个 bean:

@Configuration
class DevEnvLoggingConfiguration {
    
    @Bean
    @Conditional(IsDevEnvCondition.class)
    LoggingService loggingService() {
        return new LoggingService();
    }
}

通过这样做,我们可以根据给定的条件(例如环境类型或客户的特定需求)来确定应用程序的行为。在上面的示例中,我们仅为开发环境初始化了额外的日志服务。

使组件成为条件的另一种方法是将条件直接放在组件类上:

@Service
@Conditional(IsDevEnvCondition.class)
class LoggingService {
    // ...
}

我们可以将上面的示例应用于使用 @Component@Service@Repository@Controller 注释声明的任何 bean。

3. 预定义条件注释

Spring 附带了一组预定义的条件注释。让我们来看看一些最受欢迎的。

首先,让我们看看如何 使组件基于配置属性值

@Service
@ConditionalOnProperty(
  value="logging.enabled", 
  havingValue = "true", 
  matchIfMissing = true)
class LoggingService {
    // ...
}

第一个属性 value 告诉我们将要查看的配置属性。第二个是 havingValue, 定义此条件所需的值。最后, matchIfMissing 属性告诉 Spring 如果参数丢失,是否应该匹配条件。

同样,我们可以 将条件基于表达式

@Service
@ConditionalOnExpression(
  "${logging.enabled:true} and '${logging.level}'.equals('DEBUG')"
)
class LoggingService {
    // ...
}

现在,仅当 logging.enabled 配置属性设置为 true 并且 logging.level 设置为 DEBUG 时,Spring才会创建 LoggingService

我们可以应用的另一个条件是检查是否创建了给定的 bean:

@Service
@ConditionalOnBean(CustomLoggingConfiguration.class)
class LoggingService {
    // ...
}

或者类路径中存在给定的类:

@Service
@ConditionalOnClass(CustomLogger.class)
class LoggingService {
    // ...
}

我们可以通过应用 @ConditionalOnMissingBean@ConditionalOnMissingClass 注释来实现相反的行为。

此外,我们可以 将我们的组件依赖于给定的 Java 版本

@Service
@ConditionalOnJava(JavaVersion.EIGHT)
class LoggingService {
    // ...
}

在上面的示例中,仅当运行环境为Java 8时才会创建 LoggingService

最后,我们可以使用 @ConditionalOnWarDeployment 注解来仅在war打包中启用bean:

@Configuration
@ConditionalOnWarDeployment
class AdditionalWebConfiguration {
    // ...
}

请注意,对于具有嵌入式服务器的应用程序,此条件将返回 false。

4. 定义自定义条件

Spring 允许我们通过 创建自定义条件模板 来自定义 @Conditional 注释的行为。要创建一个,我们只需实现 Condition 接口:

class Java8Condition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
    }
}

matches 方法告诉 Spring 条件是否已通过。它有两个参数,为我们提供有关 bean 将初始化的上下文的信息,以及所使用的 @Conditional 注释的元数据。

正如我们在示例中看到的,我们只检查 Java 版本是否为 8。

之后,我们应该将新条件作为属性放置在 @Conditional 注释中:

@Service
@Conditional(Java8Condition.class)
public class Java8DependedService {
    // ...
}

这样,仅当 Java8Condition 类中的条件匹配时才会创建 Java8DependentService

5. 组合条件

对于更复杂的解决方案,我们可以 使用 OR 或 AND 逻辑运算符对条件注释进行分组

要应用 OR 运算符,我们需要创建一个扩展 AnyNestedCondition 类的自定义条件。在其中,我们需要为每个条件创建一个空的 静态 类,并使用适当的 @Conditional 实现对其进行注释。

例如,让我们创建一个需要 Java 8 或 Java 9 的条件:

class Java8OrJava9 extends AnyNestedCondition {
    
    Java8OrJava9() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }
    
    @Conditional(Java8Condition.class)
    static class Java8 { }
    
    @Conditional(Java9Condition.class)
    static class Java9 { }
    
}

另一方面,AND 运算符要简单得多。我们可以简单地将条件分组:

@Service
@Conditional({IsWindowsCondition.class, Java8Condition.class})
@ConditionalOnJava(JavaVersion.EIGHT)
public class LoggingService {
    // ...
}

在上面的示例中,仅当 IsWindowsConditionJava8Condition 都匹配时才会创建 LoggingService

六、总结

在本文中,我们学习了如何使用和创建条件注释。与往常一样,所有源代码都可以在 GitHub 上获取。