Prohibit unnecessary values on @ConditionalOnMissingBean

Closes gh-42941
This commit is contained in:
Andy Wilkinson 2024-10-30 09:58:01 +00:00
parent 1f7d8ddc3c
commit 1c61e59099
2 changed files with 38 additions and 5 deletions

View File

@ -24,16 +24,19 @@ import java.nio.file.Files;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAnnotation;
import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClass.Predicates; import com.tngtech.archunit.core.domain.JavaClass.Predicates;
import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod; import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaParameter; 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.CanBeAnnotated;
import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchCondition;
@ -83,7 +86,8 @@ public abstract class ArchitectureCheck extends DefaultTask {
noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(), noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(),
noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding(), noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding(),
noClassesShouldCallStringToUpperCaseWithoutLocale(), noClassesShouldCallStringToUpperCaseWithoutLocale(),
noClassesShouldCallStringToLowerCaseWithoutLocale()); noClassesShouldCallStringToLowerCaseWithoutLocale(),
conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType());
getRules().addAll(getProhibitObjectsRequireNonNull() getRules().addAll(getProhibitObjectsRequireNonNull()
.map((prohibit) -> prohibit ? noClassesShouldCallObjectsRequireNonNull() : Collections.emptyList())); .map((prohibit) -> prohibit ? noClassesShouldCallObjectsRequireNonNull() : Collections.emptyList()));
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList())); getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
@ -244,6 +248,36 @@ public abstract class ArchitectureCheck extends DefaultTask {
.because("org.springframework.utils.Assert.notNull(Object, Supplier) should be used instead")); .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<? super JavaMethod> 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<JavaMethod> conditional = item
.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean");
Map<String, Object> 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) { public void setClasses(FileCollection classes) {
this.classes = classes; this.classes = classes;
} }

View File

@ -506,7 +506,7 @@ class ConditionalOnMissingBeanTests {
static class ConditionalOnIgnoredSubclass { static class ConditionalOnIgnoredSubclass {
@Bean @Bean
@ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class) @ConditionalOnMissingBean(ignored = CustomExampleBean.class)
ExampleBean exampleBean() { ExampleBean exampleBean() {
return new ExampleBean("test"); return new ExampleBean("test");
} }
@ -517,7 +517,7 @@ class ConditionalOnMissingBeanTests {
static class ConditionalOnIgnoredSubclassByName { static class ConditionalOnIgnoredSubclassByName {
@Bean @Bean
@ConditionalOnMissingBean(value = ExampleBean.class, @ConditionalOnMissingBean(
ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean") ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean")
ExampleBean exampleBean() { ExampleBean exampleBean() {
return new ExampleBean("test"); return new ExampleBean("test");
@ -701,8 +701,7 @@ class ConditionalOnMissingBeanTests {
static class ParameterizedConditionWithValueConfig { static class ParameterizedConditionWithValueConfig {
@Bean @Bean
@ConditionalOnMissingBean(value = CustomExampleBean.class, @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
parameterizedContainer = TestParameterizedContainer.class)
CustomExampleBean conditionalCustomExampleBean() { CustomExampleBean conditionalCustomExampleBean() {
return new CustomExampleBean(); return new CustomExampleBean();
} }