- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
本文翻译自 https://codingnconcepts.com/spring-boot/conditional-annotations-in-spring-boot
翻译过程中会对文中英语语序和不甚明确的地方进行微调,如有谬误欢迎评论指正。
在这篇教程中,我们将通过一些示例带你了解 Spring Boot 条件注解
Spring Boot 是自以为是的,当 Spring Boot 在classpath中找到相关依赖项时,它会为模块提供默认(自动)配置。
举例,Spring Boot 提供了:
spring-cloud-starter-openfeign
依赖会提供默认的 HttpClient 配置,你可以通过在classpath中添加 OkHttpClient 或 ApacheHttpClient 依赖来改变HttpClientspring-boot-starter-web
的依赖,则使用 Jackson 框架作为默认的 JSON 序列化传输请求和响应spring-boot-starter-data-jpa
则会创建默认的数据源配置Spring Boot 使用 @Conditional
注解实现了这一点。Spring Boot 大量使用 @Conditional
注解基于条件方式来加载默认配置、Bean对象的。
这些条件可以是:
类似于Spring如何神奇地读取默认的配置,有些时候我们想基于自定义的条件来读取 Bean对象、模块 到Spring应用上下文中。
我们可以通过 @Conditional
注解+自定义的条件实现这个目标。
@Conditional 注解是用来匹配只有满足所有指定条件才能将Bean注册到Spring上下文中。
@Conditional
注解有以下三种使用方式:
我们可以在 @Bean
注解下添加 @Conditional
注解来达到满足条件注册Bean。
@Configuration
class ConditionalBeanConfiguration {
@Bean
@Conditional(CustomCondition.class) //<- method level condition
ConditionalBean conditionalBean(){
return new ConditionalBean();
};
}
我们可以在有 @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
};
}
我们可以创建一个自定义的注解,在注解上添加 @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
可匹配到。
我们可组合多个条件,实现任一条件符合,换句话讲:类似于实现 OR
的逻辑操作。
再创建一个注解来组合
我们已经创建了一个自定义注解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
能匹配到。
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 {}
}
ConfigurationPhase(配置阶段)
注意 ConfigurationPhase
参数被传递到了构建方法:
@Configuration
类,使用 ConfigurationPhase.PARSE_CONFIGURATION
;@Bean
对象上,使用 ConfigurationPhase.REGISTER_BEAN
Spring Boot 需要区分这以上两种作用域以便在合适的时间应用这些条件。
使用任一条件匹配
让我们来应用组合条件到一个配置类吧:
@Configuration
@Conditional(CombinedConditionsWithAnyMatch.class)
class ConditionalConfiguration {
@Bean
Bean bean(){
// TODO
};
}
当任一条件满足时,这个配置类对象将被加载到Spring应用上下文中
application.yml
custom.condition.enabled: true
another-custom.condition.enabled: false
我们可以组合多个条件来达到仅当所有条件都满足时才匹配的目的,类似于 AND
的逻辑操作。
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 {}
}
使用完全匹配条件
这次我们将 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
我们可以通过扩展 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...
便利注解供我们使用。让我们看看有哪些:
这个是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
如果我们需要多个配置项组合更复杂的条件,我们可以使用 @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 的作用。
我们可能需要仅当某个Bean依赖的Bean存在应用上下文中才创建这个Bean:
@Service
@ConditionalOnBean(ApiDocConfig.class)
class ApiDocService {
// TODO
}
这个ApiDocService
仅当 ApiDocConfig
这个类的对象在应用上下文中才会加载。这个方法使我们可以定义依赖其他Bean的对象。(译者注:依赖对象存在则创建,否则不创建)
类似于上边的例子,可以使用 @ConditionalOnMissingBean
来确保应用上下文中没有某个对象才创建:
@Configuration
class DatabaseConfig {
@Bean
@ConditionalOnMissingBean
DataSource dataSource() {
return new InMemoryDataSource();
}
}
这个例子演示了当应用上下文中没有其他 DataSource
对象时,创建 InMemoryDataSource
这个对象,这和Spring Boot 在测试上下文创建内存数据源是类似的。
这个注解仅当指定资源文件在 classpath 中是能找到的,才会加载这个配置
@Configuration
@ConditionalOnResource(resources = "/logback.xml")
class LoggingConfig {
// TODO
}
这个 LoggingConfig
仅当 logback.xml
配置文件存在于 classpath 时才会加载。这样一来,我们能创建相似配置类,仅当它们的配置文件存在配置类才可用。
上面描述的条件注解是我们在 Spring Boot 项目中通常使用的最常见的注解。 Spring Boot 提供了许多其他的条件注解。 然而,它们并不常见,更适合框架开发而不是应用程序开发(Spring Boot 在后台大量使用其中的一些)。 所以,让我们在这里简单地看一下它们。
仅当指定类在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。
仅当此应用是一个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 的目的。
仅在等于或高于指定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
}
仅在指定云平台上运行应用时才会加载此注解标记的 bean
@Configuration
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
class RunsOnKubernetesCloudPlatform {
// Code runs on application running on Kubernetes
}
仅在传统 War 包打包部署时加载该注解标记的 bean。在应用以内嵌服务器运行时这个条件返回false。
@Configuration
@ConditionalOnWarDeployment
class RunsWithWarPackages {
// Code runs with WAR package deployment
}
仅在指定的 JNDI 路径存在时加载该注解标记的 bean。如果没有路径指定,则条件匹配仅基于 javax.naming.InitialContext
的存在。
@Configuration
@ConditionalOnJndi("java:comp/env/ejb/myEJB")
class RunsWithJndiLocationAvailability {
// Code runs when JNDI location is available
}
与 @ConditionalOnBean
类似,但它是仅在这个 bean 是唯一的候选时启用。如果 BeanFactory
中已包含多个匹配的 bean 实例但已定义主 @Primary
候选者,则条件也将匹配;本质上,如果自动装配具有定义类型的 bean,则条件匹配将成功。 强烈建议仅在自动配置类上使用此条件。(译者注:有一个或多个中有主候选的 bean 将匹配成功)
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
class RunsWithSingleDataSourceBean {
// Code runs when single data source bean is determined
}
仅当 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
}
当指定的管理端点(译者注:接口地址)可用时加载 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 提供,开箱即用。
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
}
仅当健康指示器配置 management.health.<name>.enabled
启用时才加载此健康检测指示器类,<name>
需要指定为具体的值。
@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 注解,因为过度使用他们会导致难于调试与管理。
本文中代码的示例在github/springboot-config
我正在努力处理查询的 WHERE 部分。查询本身包含一个基于两个表中都存在的 ID 的 LEFT JOIN。但是,我要求 where 语句仅返回其中一列中存在的最大单个结果。目前我返回连接中的所有值,
我有这个代码来改变文件系统的大小。问题是,即使满足 if 条件,它也不会进入 if 条件,而我根本没有检查 if 条件。它直接进入 else 条件。 运行代码后的结果 post-install-ray
假设我有一个包含 2 列的 Excel 表格:单元格 A1 到 A10 中的日期和 B1 到 B10 中的值。 我想对五月日期的所有值求和。我有3种可能性: {=SUM((MONTH(A1:A10)=
伪代码: SELECT * FROM 'table' WHERE ('date' row.date 或 ,我们在Stack Overflow上找到一个类似的问题: https://stackove
我有下面这行代码做一个简单的查询 if ($this->fulfilled) $criteria->addCondition('fulfilled ' . (($this->fulfilled
如果在数据库中找到用户输入的键,我将尝试显示“表”中的数据。目前我已将其设置为让数据库检查 key 是否存在,如下所示: //Select all from table if a key entry
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 5 年前。 Improve th
在MYSQL中可以吗 一共有三个表 任务(task_id、task_status、...) tasks_assigned_to(ta_id、task_id、user_id) task_suggeste
我想先根据用户的状态然后根据用户名来排序我的 sql 请求。该状态由 user_type 列设置: 1=活跃,2=不活跃,3=创始人。 我会使用此请求来执行此操作,但它不起作用,因为我想在“活跃”成员
下面两个函数中最专业的代码风格是什么? 如果函数变得更复杂和更大,例如有 20 个检查怎么办? 注意:每次检查后我都需要做一些事情,所以我不能将所有内容连接到一个 if 语句中,例如: if (veh
我在 C# 项目中使用 EntityFramework 6.1.3 和 SQL Server。我有两个查询,基本上应该执行相同的操作。 1. Exams.GroupBy(x=>x.SubjectID)
我试图在 case when 语句中放入两个条件,但我在 postgresql 中遇到语法错误 case when condition 1 and condition 2 then X else Y
我正在构建一个连接多个表的查询,一个表 prodRecipe 将包含某些行的数据,但不是全部,但是 tmp_inv1 将包含所有行的计数信息。问题是,tmp_inv1.count 取决于某个项目是否在
我有一个涉及 couples of rows which have a less-than-2-hours time-difference 的查询(~0.08333 天): SELECT mt1.*,
我有一个包含许多这样的 OR 条件的代码(工作正常)来检查其中一个值是否为空,然后我们抛出一条错误消息(所有这些都必须填写) } elsif ( !$params{'account'}
我有一个名为 spGetOrders 的存储过程,它接受一些参数:@startdate 和 @enddate。这将查询“订单”表。表中的一列称为“ClosedDate”。如果订单尚未关闭,则此列将保留
在代码中,注释部分是我需要解决的问题...有没有办法在 LINQ 中编写这样的查询?我需要这个,因为我需要根据状态进行排序。 var result = ( from contact in d
我正在尝试创建一个允许省略参数的存储过程,但如果提供了参数,则进行 AND 操作: CREATE PROCEDURE MyProcedure @LastName Varchar(30)
我正在寻找一种方法来过滤我的主机文件中的新 IP 地址。我创建了一个脚本,每次我用来自矩阵企业管理器的数据调用它时都会更新我的主机文件。它工作正常。但是我必须找到一个解决方案,只允许更新 10.XX.
所以我正在做一种 slider ,当它完全向下时隐藏向下按钮,反之亦然,当向上按钮隐藏时,我遇到了问题。 var amount = $('slide').attr('number'); $('span
我是一名优秀的程序员,十分优秀!