From 0b3ee7433175cd05b803ae74b604f879d39a89c3 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 19 Feb 2024 10:02:32 +0800 Subject: [PATCH] Introduce NegatableSpringBootCondition for negating condition Closes GH-39614 --- .../condition/ConditionalOnProperty.java | 9 +- .../NegatableSpringBootCondition.java | 58 +++++++++++++ .../condition/OnPropertyCondition.java | 3 +- .../condition/SpringBootCondition.java | 2 +- .../condition/ConditionalOnPropertyTests.java | 44 +++++++++- .../NegatableSpringBootConditionTests.java | 87 +++++++++++++++++++ 6 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootCondition.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootConditionTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.java index a4b8f947b58a..5aa87e217e40 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,6 +87,7 @@ * @author Maciej Walkowiak * @author Stephane Nicoll * @author Phillip Webb + * @author Yanming Zhou * @since 1.1.0 */ @Retention(RetentionPolicy.RUNTIME) @@ -135,4 +136,10 @@ */ boolean matchIfMissing() default false; + /** + * Specify if the condition should be negating. Defaults to {@code false}. + * @return if the condition should be negating + */ + boolean negating() default false; + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootCondition.java new file mode 100644 index 000000000000..2fe652b3dc5a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootCondition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.Annotation; +import java.util.Map; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.ResolvableType; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.Assert; + +/** + * Base of all negatable {@link Condition} implementations used with Spring Boot. + * + * @author Yanming Zhou + */ +public abstract class NegatableSpringBootCondition extends SpringBootCondition { + + public static final String NEGATING_ATTRIBUTE_NAME = "negating"; + + @Override + public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + boolean result = super.matches(context, metadata); + Map annotationAttributes = metadata.getAnnotationAttributes(getAnnotationName()); + if (annotationAttributes != null && annotationAttributes.containsKey(NEGATING_ATTRIBUTE_NAME)) { + if ((Boolean) annotationAttributes.get(NEGATING_ATTRIBUTE_NAME)) { + result = !result; + } + } + return result; + } + + protected String getAnnotationName() { + Class clazz = ResolvableType.forClass(getClass()) + .as(NegatableSpringBootCondition.class) + .getGeneric() + .resolve(); + Assert.state(clazz != null, "Type argument of NegatableSpringBootCondition should be present"); + return clazz.getName(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java index 5af58096ed80..fd933fe20651 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnPropertyCondition.java @@ -40,10 +40,11 @@ * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson + * @author Yanming Zhou * @see ConditionalOnProperty */ @Order(Ordered.HIGHEST_PRECEDENCE + 40) -class OnPropertyCondition extends SpringBootCondition { +class OnPropertyCondition extends NegatableSpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java index fbe0f229692f..c819295f5556 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/SpringBootCondition.java @@ -41,7 +41,7 @@ public abstract class SpringBootCondition implements Condition { private final Log logger = LogFactory.getLog(getClass()); @Override - public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { ConditionOutcome outcome = getMatchOutcome(context, metadata); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java index f663915ce99e..705c8541d472 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnPropertyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Andy Wilkinson + * @author Yanming Zhou */ class ConditionalOnPropertyTests { @@ -271,6 +272,30 @@ void metaAndDirectAnnotationWithAliasConditionMatchesWhenBothPropertiesAreSet() assertThat(this.context.containsBean("foo")).isTrue(); } + @Test + void conditionDoesMatchWithPropertyNotDefined() { + load(NegatingConfiguration.class); + assertThat(this.context.containsBean("foo")).isTrue(); + } + + @Test + void conditionDoesNotMatchWithPropertyDefined() { + load(NegatingConfiguration.class, "property=foo"); + assertThat(this.context.containsBean("foo")).isFalse(); + } + + @Test + void conditionDoesMatchWithPropertyNotHavingValue() { + load(NegatingConfiguration.class, "property=foo"); + assertThat(this.context.containsBean("bar")).isTrue(); + } + + @Test + void conditionDoesNotMatchWithPropertyHavingValue() { + load(NegatingConfiguration.class, "property=bar"); + assertThat(this.context.containsBean("bar")).isFalse(); + } + private void load(Class config, String... environment) { TestPropertyValues.of(environment).applyTo(this.environment); this.context = new SpringApplicationBuilder(config).environment(this.environment) @@ -289,6 +314,23 @@ String foo() { } + @Configuration(proxyBeanMethods = false) + static class NegatingConfiguration { + + @Bean + @ConditionalOnProperty(name = "property", negating = true) + String foo() { + return "foo"; + } + + @Bean + @ConditionalOnProperty(name = "property", havingValue = "bar", negating = true) + String bar() { + return "bar"; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "spring.", name = "the-relaxed-property") static class RelaxedPropertiesRequiredConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootConditionTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootConditionTests.java new file mode 100644 index 000000000000..219226e07bd3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/NegatableSpringBootConditionTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.condition; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.junit.jupiter.api.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NegatableSpringBootCondition}. + * + * @author Yanming Zhou + */ +class NegatableSpringBootConditionTests { + + @Test + void notNegating() { + ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + assertThat(context.containsBean("foo")).isTrue(); + } + + @Test + void negating() { + ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); + assertThat(context.containsBean("bar")).isFalse(); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + @AlwaysTrueConditional + String foo() { + return "bean"; + } + + @Bean + @AlwaysTrueConditional(negating = true) + String bar() { + return "bean"; + } + + } + + @Retention(RetentionPolicy.RUNTIME) + @Conditional(AlwaysTrueCondition.class) + public @interface AlwaysTrueConditional { + + boolean negating() default false; + + } + + static class AlwaysTrueCondition extends NegatableSpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + return ConditionOutcome.match(); + } + + } + +}