一、概述
Spring Boot 是自以为是的,当 Spring Boot 在classpath中找到相关依赖项时,它会为模块提供默认(自动)配置。

举例,Spring Boot 提供了:

如 classpath 中未提供服务器组件依赖,则默认启用内嵌Tomcat作为服务端依赖。你也可以把它改成 Jetty 或 Undertow,仅需要添加相应的依赖到 classpath 中(译者注:常见的方式就是添加Maven或Gradle依赖)
当它在classpath找到 spring-cloud-starter-openfeign 依赖会提供默认的 HttpClient 配置,你可以通过在classpath中添加 OkHttpClient 或 ApacheHttpClient 依赖来改变HttpClient
当classpath中包含 spring-boot-starter-web 的依赖,则使用 Jackson 框架作为默认的 JSON 序列化传输请求和响应
当classpath中包含 spring-boot-starter-data-jpa 则会创建默认的数据源配置
1.1、Spring Boot 是如何做到这一切的呢?

Spring Boot 使用 @Conditional 注解实现了这一点。Spring Boot 大量使用 @Conditional 注解基于条件方式来加载默认配置、Bean对象的。

这些条件可以是:

classpath中可用的依赖、配置文件内容 或 类
定义在 application.yml 或 application.properties 文件、系统配置、环境变量中的配置
Java的版本、操作系统、云平台、web应用 等。
1.2、它对我们有什么用处呢?

类似于Spring如何神奇地读取默认的配置,有些时候我们想基于自定义的条件来读取 Bean对象、模块 到Spring应用上下文中。

我们可以通过 @Conditional 注解+自定义的条件实现这个目标。

二、@Conditional 注解说明
@Conditional 注解是用来匹配只有满足所有指定条件才能将Bean注册到Spring上下文中。

@Conditional 注解有以下三种使用方式:

2.1、作为方法级的注解

我们可以在 @Bean 注解下添加 @Conditional 注解来达到满足条件注册Bean。

@Configuration
class ConditionalBeanConfiguration {
  @Bean
  @Conditional(CustomCondition.class) //<- method level condition
  ConditionalBean conditionalBean(){
    return new ConditionalBean();
  };
}
2.2、作为类级别注解

我们可以在有 @Component, @Service, @Repository, @Controller, @RestController, 或 @Configuration 注解的类上标注 @Conditional,仅当满足条件才能将这个类型的 bean 注册到 Spring 上下文中。

@Component
@Conditional(CustomCondition.class) //<- class level condition
class ConditionalComponent {
}
带@Conditional注解的@Configuration类

如果一个 @Configuration 注解标识的类头包含 @Conditional 则这个类所有的 @Bean 方法、@Import 注解 和 @ComponentScan 注解都会被类头上的 @Conditional 影响,仅当条件满足时才会被加载。

@Configuration
@Conditional(CustomCondition.class) //<- class level condition
class ConditionalConfiguration {
  @Bean //<- will be loaded only if class level condition is met
  Bean bean(){
    // Code for bean definition
  };
}
2.3、作为源注解

我们可以创建一个自定义的注解,在注解上添加 @Conditional 作为新的条件注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(CustomCondition.class) //<- meta annotation condition
public @interface CustomConditionAnnotation {
}
现在我们可在方法和类上使用自定义的 @CustomConditionAnnotation 注解代替@Conditional 就像这样:

@Configuration
class ConditionalBeanConfiguration {
  @Bean
  @CustomConditionAnnotation  //<- custom annotation at method level
  ConditionalBean conditionalBean(){
    return new ConditionalBean();
  };
}

@Component
@CustomConditionAnnotation //<- custom annotation at class level
class ConditionalComponent {
}
三、自定义条件
在之前的例子中,我们使用 CustomCondition.class 中的 @Conditional 注解实现了条件匹配的逻辑。

让我们通过实现 Spring 的 Condition 接口的 matches 方法创建自定义条件吧:

public class CustomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("custom.condition.enabled", Boolean.class, false);
    }
}
配置文件:application.yml

custom.condition.enabled: true

我们可以看到当 custom.condition.enabled 设为 true 时,CustomCondition 可匹配到。

3.1、组合条件-任一条件成立匹配

我们可组合多个条件,实现任一条件符合,换句话讲:类似于实现 OR 的逻辑操作。

3.1.1、再创建一个注解来组合

我们已经创建了一个自定义注解CustomCondition,让我们快速创建一个AnotherCustomCondition注解:

public class AnotherCustomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("another-custom.condition.enabled", Boolean.class, false);
    }
}
配置文件:application.yml

another-custom.condition.enabled: true

我们看到 another-custom.condition.enabled 条件为 true 时, AnotherCustomCondition 能匹配到。

3.1.2、AnyNestedCondition(任意嵌套条件)

现在让我们通过扩展 Spring 的 AnyNestedCondition 类来组合上边两个注解,做到任一匹配:

public class CombinedConditionsWithAnyMatch extends AnyNestedCondition {

public CombinedConditionsWithAnyMatch() {
        super(ConfigurationPhase.PARSE_CONFIGURATION);
        // super(ConfigurationPhase.REGISTER_BEAN);
    }

@Conditional(CustomCondition.class)
    static class OnCustomCondition {}

@Conditional(AnotherCustomCondition.class)
    static class OnAnotherCustomCondition {}
}
3.1.3、ConfigurationPhase(配置阶段)说明

注意 ConfigurationPhase 参数被传递到了构建方法:

如果你想组合的条件到一个 @Configuration 类,使用 ConfigurationPhase.PARSE_CONFIGURATION;

如果你想把条件应用到 @Bean 对象上,使用 ConfigurationPhase.REGISTER_BEAN

Spring Boot 需要区分这以上两种作用域以便在合适的时间应用这些条件。

3.1.4、使用任一条件匹配

让我们来应用组合条件到一个配置类吧:

@Configuration
@Conditional(CombinedConditionsWithAnyMatch.class)
class ConditionalConfiguration {
  @Bean
  Bean bean(){
    // TODO
  };
}
当任一条件满足时,这个配置类对象将被加载到Spring应用上下文中

配置文件:application.yml

custom.condition.enabled: true

another-custom.condition.enabled: false
3.2、组合条件-所有条件成立匹配

我们可以组合多个条件来达到仅当所有条件都满足时才匹配的目的,类似于 AND 的逻辑操作。

3.2.1、AllNestedConditions 基础类

这次我们组合 CustomCondition、AnotherCustomCondition 这两个注解,通过扩展Spring的 AllNestedConditions 创建 匹配所有 (完全匹配)的条件类

public class CombinedConditionsWithAllMatch extends AllNestedConditions {
    public CombinedConditionsWithAllMatch() {
        // super(ConfigurationPhase.PARSE_CONFIGURATION);
        super(ConfigurationPhase.REGISTER_BEAN);
    }
    @Conditional(CustomCondition.class)
    static class OnCustomCondition {}
    @Conditional(AnotherCustomCondition.class)
    static class OnAnotherCustomCondition {}
}
3.2.2、使用完全匹配条件

这次我们将 ConfigurationPhase.REGISTER_BEAN 传入构造方法,我们就能使用这个组合条件放到 @Bean 对象,就像下边这样:

@Configuration
class ConditionalBeanConfiguration {
  @Bean
  @Conditional(CombinedConditionsWithAllMatch.class) //<- as method level annotation
  ConditionalBean conditionalBean(){
    return new ConditionalBean();
  };
}
这个Bean仅当所有条件都满足时才会加载到Spring应用上下文中:

配置文件:application.yml

custom.condition.enabled: true

another-custom.condition.enabled: true
3.3、组合条件-所有条件不成立匹配

我们可以通过扩展 Spring 的 NoneNestedCondition 类来组合出所有条件都不成立的条件:

public class CombinedConditionsWithNoneMatch extends NoneNestedConditions {
    public CombinedConditionsWithNoneMatch() {
        super(ConfigurationPhase.PARSE_CONFIGURATION);
        // or super(ConfigurationPhase.REGISTER_BEAN);
    }

@Conditional(CustomCondition.class)
    static class OnCustomCondition {}

@Conditional(AnotherCustomCondition.class)
    static class OnAnotherCustomCondition {}
}
以上组合条件将会成功匹配如下配置:

配置文件:application.yml

custom.condition.enabled: false

another-custom.condition.enabled: false
四、预定义的条件注解
Spring Boot 提供了一系列预定义的 @ConditionalOn… 便利注解供我们使用。让我们看看有哪些:

4.1、@ConditionalOnProperty

这个是Spring Boot中最常用的注解,它允许通过指定的配置来条件式加载类或Bean:

@Configuration
@ConditionalOnProperty(
    value="api.doc.enabled", 
    havingValue = "true", 
    matchIfMissing = true)
class ApiDocConfig {
  // TODO
}
ApiDocConfig 仅当 api.doc.enabled: true 时才会加载,如果没有配置这个参数,它也会被加载,因为定义了 matchIfMissing = true,通过这种方式,我们可以在没配置指定参数时创建默认配置,设置为false时才禁用。

一个常见的例子是当我们想去在开发环境开启一个指定配置,而生产环境禁用,反之亦然:

配置文件:application-dev.yml

api.doc.enabled: true

配置文件:application-prod.yml

api.doc.enabled: false

(译者注:通过不同配置文件区分环境,由条件注解判断是否加载类或对象到Spring上下文)

另一个常见的例子是定义了一个公共组件,通过不同项目的配置文件可以在开启或禁用这个功能,配置示例如下:

Project A -> application.yml
api.doc.enabled: true

Project B -> application.yml
api.doc.enabled: false
4.2、@ConditionalOnExpression

如果我们需要多个配置项组合更复杂的条件,我们可以使用 @ConditionalOnExpression (译者注:表达式条件注解):

@Configuration
@ConditionalOnExpression(
  "${api.doc.enabled:true} and '${spring.profile.active}'.equalsIgnoreCase('DEV')"
)
class ApiDocConfig {
  // TODO
}
这个 ApiDocConfig 仅当 api.doc.enabled: true 并且 spring.profile.active: dev 时才会启用。我们告诉 Spring Boot 使用 true作为默认值适用于参数未设置的场景。

我们可以在这个注解中更充分地发挥 Spring Expression Language 的作用。

4.3、@ConditionalOnBean

我们可能需要仅当某个Bean依赖的Bean存在应用上下文中才创建这个Bean:

@Service
@ConditionalOnBean(ApiDocConfig.class)
class ApiDocService {
    // TODO
}
这个ApiDocService 仅当 ApiDocConfig 这个类的对象在应用上下文中才会加载。这个方法使我们可以定义依赖其他Bean的对象。(译者注:依赖对象存在则创建,否则不创建)

4.4、@ConditionalOnMissingBean

类似于上边的例子,可以使用 @ConditionalOnMissingBean 来确保应用上下文中没有某个对象才创建:

@Configuration
class DatabaseConfig {
  @Bean
  @ConditionalOnMissingBean
  DataSource dataSource() {
    return new InMemoryDataSource();
  }
}
这个例子演示了当应用上下文中没有其他 DataSource 对象时,创建 InMemoryDataSource这个对象,这和Spring Boot 在测试上下文创建内存数据源是类似的。

4.5、@ConditionalOnResource

这个注解仅当指定资源文件在 classpath 中是能找到的,才会加载这个配置

@Configuration
@ConditionalOnResource(resources = "/logback.xml")
class LoggingConfig {
  // TODO
}
这个 LoggingConfig 仅当 logback.xml 配置文件存在于 classpath 时才会加载。这样一来,我们能创建相似配置类,仅当它们的配置文件存在配置类才可用。

上面描述的条件注解是我们在 Spring Boot 项目中通常使用的最常见的注解。 Spring Boot 提供了许多其他的条件注解。 然而,它们并不常见,更适合框架开发而不是应用程序开发(Spring Boot 在后台大量使用其中的一些)。 所以,让我们在这里简单地看一下它们。

4.6、@ConditionalOnClass

仅当指定类在classpath下才会加载这个bean,我们可指定类的全路径或这个类的名称

@Service
@ConditionalOnClass(name = "com.example.config.LoggingConfig")
class LoggingService {
  // Code runs when class in available in classpath
}

@Service
@ConditionalOnClass(LoggingConfig.class)
class LoggingService {
  // Code runs when class in available in classpath
}
相似的,我们也可以用 @ConditionalOnMissingClass 注解来加载一个需要某个类不在classpath 下时才加载的bean。

4.7、@ConditionalOnWebApplication

仅当此应用是一个web应用时加载这个bean,默认只要是web容器都会匹配,也可以使用type 属性来缩小范围。(译者注:type 共有3种,ANY 表示任何 web 容器,范围最大;SERVLET表示仅 servlet 容器;“REACTIVE` 表示仅响应式容器)

@Configuration
@ConditionalOnWebApplication
class RunsOnAnyWebApplication {
  // Code runs on web application
}

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
class RunsOnServletBasedWebApplication {
  // Code runs on Servlet based web application
}
类似地,也可以用 @ConditionalOnNotWebApplication 来达到非 Web 容器才加载 bean 的目的。

4.8、@ConditionalOnJava

仅在等于或高于指定JVM版本时才加载某个 bean。默认是等于或高于指定版本,也可以设置为 range 属性表示一个版本区间。

@Configuration
@ConditionalOnJava(JavaVersion.EIGHT)
class RunsOnJavaEightAndAbove {
  // Code runs on Java-8 and above versions
}

@Configuration
@ConditionalOnJava(value = JavaVersion.ELEVEN, range = ConditionalOnJava.Range.OLDER_THAN)
class RunsOnBelowJavaEleven {
  // Code runs below Java-11. 
  // Code doesn't run on Java-11 and above versions
}
4.9、@ConditionalOnCloudPlatform

仅在指定云平台上运行应用时才会加载此注解标记的 bean

@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
class RunsOnKubernetesCloudPlatform {
  // Code runs on application running on Kubernetes
}
4.10、@ConditionalOnWarDeployment

仅在传统 War 包打包部署时加载该注解标记的 bean。在应用以内嵌服务器运行时这个条件返回false。

@Configuration
@ConditionalOnWarDeployment
class RunsWithWarPackages {
  // Code runs with WAR package deployment
}
4.11、@ConditionalOnJndi

仅在指定的 JNDI 路径存在时加载该注解标记的 bean。如果没有路径指定,则条件匹配仅基于 javax.naming.InitialContext 的存在。

@Configuration
@ConditionalOnJndi("java:comp/env/ejb/myEJB")
class RunsWithJndiLocationAvailability {
  // Code runs when JNDI location is available
}
4.12、@ConditionalOnSingleCandidate

与 @ConditionalOnBean 类似,但它是仅在这个 bean 是唯一的候选时启用。如果 BeanFactory 中已包含多个匹配的 bean 实例但已定义主 @Primary 候选者,则条件也将匹配;本质上,如果自动装配具有定义类型的 bean,则条件匹配将成功。 强烈建议仅在自动配置类上使用此条件。(译者注:有一个或多个中有主候选的 bean 将匹配成功)

@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
class RunsWithSingleDataSourceBean {
  // Code runs when single data source bean is determined
}
4.13、@ConditionalOnManagementPort

仅当 management.server.port条件与 server.port 具体某种关系时加载bean。

有三种关系类型:

ManagementPortType.DISABLED:禁用或没定义管理端口
ManagementPortType.SAME:服务端口与管理端口相同
ManagementPortType.DIFFERENT:服务与管理端口不同
@Configuration
@ConditionalOnManagementPort(ManagementPortType.DISABLED)
class ManagementPortIsDisabled {
  // Code runs when management port is disabled
}

@Configuration
@ConditionalOnManagementPort(ManagementPortType.SAME)
class ManagementPortIsSameAsServerPort {
  // Code runs when management port is same as server port
}

@Configuration
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
class ManagementPortIsDifferentFromServerPort {
  // Code runs when management port is different from server port
}

4.14、@ConditionalOnAvailableEndpoint

当指定的管理端点(译者注:接口地址)可用时加载 bean。如果一个端点被单独启用或使用 management.endpoints.web.exposure.include 暴露出来,都视为可用。

@Configuration
@ConditionalOnAvailableEndpoint(endpoint = InfoEndpoint.class)
class InfoEndpointIsAvailable {
  // Code runs when info management endpoint in enabled and exposed
}
端点是由 @Endpoint 或 @EndpointExtension 标记的bean。info 端点由 Spring Boot 提供,开箱即用。

4.14.1、Custom Management Endpoint

你也可以创建自定义的管理端点,就像这个例子:

@Component
@Endpoint(id = "custom-endpoint")
public class CustomEndpoint {
    @ReadOperation
    public String print() {
        return "This is custom management endpoint";
    }
}
现在让我们写一个仅当自定义端点可用时加载的配置类:

@Configuration
@ConditionalOnAvailableEndpoint(endpoint = CustomEndpoint.class)
class CustomEndpointConfiguration {
  // Configuration for custom endpoint
}
4.15、@ConditionalOnEnabledHealthIndicator

仅当健康指示器配置 management.health. .enabled 启用时才加载此健康检测指示器类, 需要指定为具体的值。

@Configuration
@ConditionalOnEnabledHealthIndicator(value = "heartbeat")
class HeatbeatHealthIndicator {
  // Code runs when management.health.heartbeat.enabled property is set to true.
}
五、总结
Conditional 注解给了 Spring Boot 提供了自以为是的配置(译者注:默认配置),给我们提供基于 @Conditional 自定义条件加载类的灵活性,以及非常便捷的 @ConditionalOn… 注解。

我们也可通过 AllNestedConditions、AnyNestedCondition、NoneNestedCondition 组合自定义的条件,它们为我们提供了基于环境和其他条件的模块化编码方式。

我们应该谨慎地使用 Conditional 注解,因为过度使用他们会导致难于调试与管理。

Spring Boot 条件注解@Conditional介绍相关推荐

  1. 40 个 Spring Boot 常用注解

    以下文章来源方志朋的博客,回复"666"获面试宝典 作者 | 谭朝红 链接 | ramostear.com 一.Spring Web MVC 与 Spring Bean 注解 Sp ...

  2. Spring Boot 核心注解与配置文件

    一.入口类与@SpringBootApplication 注解 Spring Boot项目都会有一个*Application 类,这个类作为Spring Boot 项目的入口类,在这个入口类中有mai ...

  3. idea提示未配置 Spring Boot 配置注解处理器解决方法

    未配置 Spring Boot 配置注解处理器 解决方法: 在pom.xml里添加依赖 <dependency><groupId>org.springframework.boo ...

  4. Spring基础:快速入门spring boot(7):spring boot 2.0简单介绍

    从这篇文章开始以spring boot2为主要版本进行使用介绍. Spring boot 2特性 spring boot2在如下的部分有所变化和增强,相关特性在后续逐步展开. 特性增强 基础组件升级: ...

  5. spring boot 相关注解

    spring boot是基于spring 开发的,因此,spring boot工程中可以使用spring 的注解.除了spring注解外,spring boot会使用到的注解有: @SpringBoo ...

  6. Spring Boot Transactional注解源码阅读笔记(二)

      在源码笔记(一)中,我们留下了几个问题: Spring Boot是怎么扫描到我们的bean里面有 Transactional 这个注解,并且把 InfrastructureAdvisorAutoP ...

  7. Spring Boot核心注解讲解

    Spring Boot最大的特点是无需XML配置文件,能自动扫描包路径装载并注入对象,并能做到根据classpath下的jar包自动配置. 所以Spring Boot最核心的3个注解就是: 1,@配置 ...

  8. spring boot actuator监控详细介绍一(超级详细)

    spring boot actuator介绍 Spring Boot包含许多其他功能,可帮助您在将应用程序推送到生产环境时监视和管理应用程序. 您可以选择使用HTTP端点或JMX来管理和监视应用程序. ...

  9. 搞懂分布式技术14:Spring Boot使用注解集成Redis缓存

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a724888/article/details/80785403 为了提高性能,减少数据库的压力,使用 ...

最新文章

  1. 多迪人事主管揭秘:面试官是如何面试Web前端求职者?
  2. DL之VGG16:基于VGG16(Keras)利用Knifey-Spoony数据集对网络架构FineTuning
  3. .net core实现跨域
  4. mongodb模糊查询_AWS 回击了!推出兼容 MongoDB 的 DocumentDB
  5. excel筛选排序从小到大_excel表格怎么按字数的多少来排列!
  6. linux标准I/O——按行输入和输出
  7. NO.10章 图(遍历、最短路、生成树、拓扑、关键路径)
  8. 【已解决】抱歉,由于某种原因,PowerPoint 无法加载D:\mathtype\Office Support\64\MathType(PowerPoint 2016).ppam加载项。
  9. 插头DP题目泛做(为了对应WYD的课件)
  10. 工程介绍好处费性质_中间人拿工程好处费是否违法
  11. react native中使用 react-native-easy-toast 和react-native-htmlview
  12. linux命令halt之后怎么开启,Linux中halt命令起什么作用呢?
  13. 记录MATLAB的s函数的使用(一)
  14. 霍兰德SE型人格如何选择专业?高考志愿填报选专业
  15. 有关PyCharm的破解安装
  16. C++ 笔试面试题 ~[有答案]
  17. Linux 性能监控分析
  18. 月薪两万的“土豪”师兄,加个微信吧!
  19. TypeError: Object of type 'datetime' is not JSON serializable
  20. rmd中无法打开链结r_十个超级好用的R语言编程技巧,一般人绝不知道!

热门文章

  1. ### CausDriver com.mysql.jdbc.Driver claims to not accept jdbcUrl, mysql:jdbc://localhost:3306/mysql
  2. FAW参考应用程序,一汽大特点-SEO狼术
  3. c++ 独一无二的不可拷贝拷贝对象
  4. python虫虫(抖音歌曲大全)
  5. qq邮箱单次群发邮件的人数太少怎么办
  6. Incorrect string value错误解决
  7. NSTimeInterval时间戳对比判断 昨天、今天
  8. 银行数字化转型导师坚鹏:金融数字化转型助力乡村振兴及案例
  9. CSM认证、CSPO认证 哪种认证适合您?
  10. 机器学习历史上诞生过很多基于游戏环境的知名AI模式