diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java index abb0be2218c..b44e9e4a24b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -24,17 +24,20 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Supplier; import java.util.stream.Collectors; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaCall; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClass.Predicates; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaParameter; +import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; import com.tngtech.archunit.core.domain.properties.HasName; import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With; @@ -90,7 +93,8 @@ public abstract class ArchitectureCheck extends DefaultTask { noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(), noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding(), noClassesShouldLoadResourcesUsingResourceUtils(), noClassesShouldCallStringToUpperCaseWithoutLocale(), - noClassesShouldCallStringToLowerCaseWithoutLocale()); + noClassesShouldCallStringToLowerCaseWithoutLocale(), + conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType()); getRules().addAll(getProhibitObjectsRequireNonNull() .map((prohibit) -> prohibit ? noClassesShouldCallObjectsRequireNonNull() : Collections.emptyList())); getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList())); @@ -263,6 +267,36 @@ public abstract class ArchitectureCheck extends DefaultTask { .because("org.springframework.utils.Assert.notNull(Object, Supplier) should be used instead")); } + private ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean") + .should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType()) + .allowEmptyShould(true); + } + + private ArchCondition notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType() { + return new ArchCondition<>("not specify only a type that is the same as the method's return type") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + JavaAnnotation conditional = item + .getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); + Map properties = conditional.getProperties(); + if (!properties.containsKey("type") && !properties.containsKey("name")) { + conditional.get("value").ifPresent((value) -> { + JavaType[] types = (JavaType[]) value; + if (types.length == 1 && item.getReturnType().equals(types[0])) { + events.add(SimpleConditionEvent.violated(item, conditional.getDescription() + + " should not specify only a value that is the same as the method's return type")); + } + }); + } + } + + }; + } + public void setClasses(FileCollection classes) { this.classes = classes; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java index cff01060ac3..866a9ebd0bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java @@ -506,7 +506,7 @@ class ConditionalOnMissingBeanTests { static class ConditionalOnIgnoredSubclass { @Bean - @ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class) + @ConditionalOnMissingBean(ignored = CustomExampleBean.class) ExampleBean exampleBean() { return new ExampleBean("test"); } @@ -517,7 +517,7 @@ class ConditionalOnMissingBeanTests { static class ConditionalOnIgnoredSubclassByName { @Bean - @ConditionalOnMissingBean(value = ExampleBean.class, + @ConditionalOnMissingBean( ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean") ExampleBean exampleBean() { return new ExampleBean("test"); @@ -701,8 +701,7 @@ class ConditionalOnMissingBeanTests { static class ParameterizedConditionWithValueConfig { @Bean - @ConditionalOnMissingBean(value = CustomExampleBean.class, - parameterizedContainer = TestParameterizedContainer.class) + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) CustomExampleBean conditionalCustomExampleBean() { return new CustomExampleBean(); }