From 048ad42b0ac642739ace0b507981f1570934f014 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Thu, 20 Jan 2022 13:21:45 +0200 Subject: [PATCH 1/6] Add ConditionalOnBean support for generic bean argument types --- .../condition/ConditionalOnBean.java | 17 ++ .../condition/ConditionalOnMissingBean.java | 17 ++ .../condition/OnBeanCondition.java | 58 ++++- .../condition/ConditionalOnBeanTests.java | 198 ++++++++++++++- .../ConditionalOnMissingBeanTests.java | 225 ++++++++++++++++-- 5 files changed, 472 insertions(+), 43 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java index 7d5ca163a162..66f1e3820fc2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java @@ -56,6 +56,7 @@ * another auto-configuration, make sure that the one using this condition runs after. * * @author Phillip Webb + * @author Uladzislau Seuruk * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @@ -110,4 +111,20 @@ */ Class<?>[] parameterizedContainer() default {}; + /** + * The class types of generic bean type arguments that should be checked. The + * condition matches when beans of all classes specified are contained in the + * {@link BeanFactory}. + * @return the class types of beans to check + */ + Class<?>[] typeArguments() default {}; + + /** + * The class type names of generic bean type arguments that should be checked. The + * condition matches when beans of all classes specified are contained in the + * {@link BeanFactory}. + * @return the class type names of beans to check + */ + String[] typeArgumentNames() default {}; + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java index c08e6200c7bb..d9dfaa122553 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java @@ -57,6 +57,7 @@ * * @author Phillip Webb * @author Andy Wilkinson + * @author Uladzislau Seuruk * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @@ -126,4 +127,20 @@ */ Class<?>[] parameterizedContainer() default {}; + /** + * The class types of generic bean type arguments that should be checked. The + * condition matches when no bean of each class specified is contained in the + * {@link BeanFactory}. + * @return the class types of beans to check + */ + Class<?>[] typeArguments() default {}; + + /** + * The class type names of generic bean type arguments that should be checked. The + * condition matches when no bean of each class specified is contained in the + * {@link BeanFactory}. + * @return the class type names of beans to check + */ + String[] typeArgumentNames() default {}; + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index 756068f5b13d..f9585f007990 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -69,6 +69,7 @@ * @author Jakub Kubrynski * @author Stephane Nicoll * @author Andy Wilkinson + * @author Uladzislau Seuruk * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnSingleCandidate @@ -156,6 +157,7 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers(); + Set<Class<?>> typeArguments = spec.getTypeArguments(); if (spec.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, @@ -167,7 +169,7 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s spec.getIgnoredTypes(), parameterizedContainers); for (String type : spec.getTypes()) { Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, - parameterizedContainers); + parameterizedContainers, typeArguments); Iterator<String> iterator = typeMatches.iterator(); while (iterator.hasNext()) { String match = iterator.next(); @@ -209,17 +211,18 @@ private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, Listab Set<String> result = null; for (String ignoredType : ignoredTypes) { Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, - ignoredType, parameterizedContainers); + ignoredType, parameterizedContainers, Collections.emptySet()); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, - ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError { + ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers, + Set<Class<?>> typeArguments) throws LinkageError { try { return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader), - parameterizedContainers); + parameterizedContainers, typeArguments); } catch (ClassNotFoundException | NoClassDefFoundError ex) { return Collections.emptySet(); @@ -227,29 +230,53 @@ private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean conside } private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type, - Set<Class<?>> parameterizedContainers) { + Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments) { Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers, - null); + typeArguments, null); return (result != null) ? result : Collections.emptySet(); } - private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, - Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) { + private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory beanFactory, Class<?> type, + Set<Class<?>> parameterizedContainers, Set<String> result) { result = addAll(result, beanFactory.getBeanNamesForType(type, true, false)); for (Class<?> container : parameterizedContainers) { ResolvableType generic = ResolvableType.forClassWithGenerics(container, type); result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); } + return result; + } + + private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, + Class<?> type, Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) { + if (!typeArguments.isEmpty()) { + result = collectBeanNamesWithTypeArguments(beanFactory, type, parameterizedContainers, typeArguments, + result); + } + else { + result = collectBeanNamesWithoutTypeArguments(beanFactory, type, parameterizedContainers, result); + } if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); if (parent instanceof ListableBeanFactory) { result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type, - parameterizedContainers, result); + parameterizedContainers, typeArguments, result); } } return result; } + private Set<String> collectBeanNamesWithTypeArguments(ListableBeanFactory beanFactory, Class<?> type, + Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) { + Class<?>[] typeArgumentArray = typeArguments.toArray(Class<?>[]::new); + ResolvableType genericType = ResolvableType.forClassWithGenerics(type, typeArgumentArray); + result = addAll(result, beanFactory.getBeanNamesForType(genericType, true, false)); + for (Class<?> container : parameterizedContainers) { + ResolvableType generic = ResolvableType.forClassWithGenerics(container, genericType); + result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); + } + return result; + } + private Set<String> getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError { Set<String> result = null; @@ -407,6 +434,8 @@ private static class Spec<A extends Annotation> { private final Set<Class<?>> parameterizedContainers; + private final Set<Class<?>> typeArguments; + private final SearchStrategy strategy; Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations, @@ -421,6 +450,7 @@ private static class Spec<A extends Annotation> { this.annotations = extract(attributes, "annotation"); this.ignoredTypes = extract(attributes, "ignored", "ignoredType"); this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer")); + this.typeArguments = resolveWhenPossible(extract(attributes, "typeArguments", "typeArgumentNames")); this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null); Set<String> types = extractTypes(attributes); BeanTypeDeductionException deductionException = null; @@ -584,6 +614,10 @@ Set<String> getIgnoredTypes() { return this.ignoredTypes; } + Set<Class<?>> getTypeArguments() { + return this.typeArguments; + } + Set<Class<?>> getParameterizedContainers() { return this.parameterizedContainers; } @@ -601,6 +635,7 @@ public String toString() { boolean hasNames = !this.names.isEmpty(); boolean hasTypes = !this.types.isEmpty(); boolean hasIgnoredTypes = !this.ignoredTypes.isEmpty(); + boolean hasTypeArguments = !this.typeArguments.isEmpty(); StringBuilder string = new StringBuilder(); string.append("("); if (hasNames) { @@ -618,6 +653,11 @@ public String toString() { string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes)); string.append("; "); } + if (hasTypeArguments) { + string.append("argument types: "); + string.append(StringUtils.collectionToCommaDelimitedString(this.typeArguments)); + string.append("; "); + } string.append("SearchStrategy: "); string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH)); string.append(")"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index 5bfb79e00d00..453d3a94b467 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -51,6 +51,7 @@ * * @author Dave Syer * @author Stephane Nicoll + * @author Uladzislau Seuruk */ class ConditionalOnBeanTests { @@ -226,6 +227,98 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); } + @Test + void genericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class, + GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean"))); + } + + @Test + void genericWhenTypeArgumentNameNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfig.class, GenericWithStringTypeArgumentNamesConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); + } + + @Test + void genericWhenTypeArgumentNameMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, GenericWithStringTypeArgumentNamesConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericStringNameExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean", + "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean", + "parameterizedContainerGenericWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean", + "parameterizedContainerGenericWithValueExampleBean"))); + } + private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -363,11 +456,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, } - static class ExampleFactoryBean implements FactoryBean<ExampleBean> { + static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> { @Override - public ExampleBean getObject() { - return new ExampleBean("fromFactory"); + public ExampleBean<String> getObject() { + return new ExampleBean<>("fromFactory"); } @Override @@ -485,23 +578,110 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class GenericWithStringConfig { + + @Bean + ExampleBean<String> genericStringExampleBean() { + return new ExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfig { + + @Bean + @ConditionalOnBean(typeArguments = String.class) + ExampleBean<String> genericStringTypeArgumentsExampleBean() { + return new ExampleBean<>("genericStringTypeArgumentsExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentNamesConfig { + + @Bean + @ConditionalOnBean(typeArgumentNames = "java.lang.String") + ExampleBean<String> genericStringNameExampleBean() { + return new ExampleBean<>("genericStringNameExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerConfig { + + @Bean + ExampleBean<Integer> genericIntegerExampleBean() { + return new ExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnBean(typeArguments = Integer.class) + ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { + return new ExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnBean(value = ExampleBean.class, typeArguments = String.class) + ExampleBean<String> genericStringWithValueExampleBean() { + return new ExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class, typeArguments = String.class) + TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig { + + @Bean + @ConditionalOnBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class, + typeArguments = String.class) + TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + @TestAnnotation - static class ExampleBean { + static class ExampleBean<T> { - private String value; + private final T value; - ExampleBean(String value) { + ExampleBean(T value) { this.value = value; } @Override public String toString() { - return this.value; + return String.valueOf(this.value); } } - static class CustomExampleBean extends ExampleBean { + static class CustomExampleBean extends ExampleBean<String> { CustomExampleBean() { super("custom subclass"); @@ -509,7 +689,7 @@ static class CustomExampleBean extends ExampleBean { } - static class OtherExampleBean extends ExampleBean { + static class OtherExampleBean extends ExampleBean<String> { OtherExampleBean() { super("other subclass"); 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 f7087dd2331c..d1052e08d5bf 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 @@ -59,6 +59,7 @@ * @author Phillip Webb * @author Jakub Kubrynski * @author Andy Wilkinson + * @author Uladzislau Seuruk */ @SuppressWarnings("resource") class ConditionalOnMissingBeanTests { @@ -340,6 +341,112 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); } + @Test + void genericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenTypeArgumentNameNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, + GenericWithStringTypeArgumentNamesConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("genericIntegerExampleBean", "genericStringNameExampleBean"))); + } + + @Test + void genericWhenTypeArgumentNameMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + GenericWithStringTypeArgumentNamesConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean", + "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean", + "parameterizedContainerGenericWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -353,8 +460,8 @@ static class OnBeanInAncestorsConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.ANCESTORS) - ExampleBean exampleBean2() { - return new ExampleBean("test"); + ExampleBean<String> exampleBean2() { + return new ExampleBean<String>("test"); } } @@ -386,7 +493,7 @@ String bar() { static class FactoryBeanConfiguration { @Bean - FactoryBean<ExampleBean> exampleBeanFactoryBean() { + FactoryBean<ExampleBean<String>> exampleBeanFactoryBean() { return new ExampleFactoryBean("foo"); } @@ -412,7 +519,7 @@ static class ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration { static class FactoryBeanWithBeanMethodArgumentsConfiguration { @Bean - FactoryBean<ExampleBean> exampleBeanFactoryBean(@Value("${theValue}") String value) { + FactoryBean<ExampleBean<String>> exampleBeanFactoryBean(@Value("${theValue}") String value) { return new ExampleFactoryBean(value); } @@ -503,8 +610,8 @@ static class ConditionalOnFactoryBean { @Bean @ConditionalOnMissingBean(ExampleBean.class) - ExampleBean createExampleBean() { - return new ExampleBean("direct"); + ExampleBean<String> createExampleBean() { + return new ExampleBean<>("direct"); } } @@ -514,8 +621,8 @@ static class ConditionalOnIgnoredSubclass { @Bean @ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class) - ExampleBean exampleBean() { - return new ExampleBean("test"); + ExampleBean<String> exampleBean() { + return new ExampleBean<>("test"); } } @@ -526,8 +633,8 @@ static class ConditionalOnIgnoredSubclassByName { @Bean @ConditionalOnMissingBean(value = ExampleBean.class, ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean") - ExampleBean exampleBean() { - return new ExampleBean("test"); + ExampleBean<String> exampleBean() { + return new ExampleBean<>("test"); } } @@ -601,8 +708,8 @@ String bar() { static class ExampleBeanConfiguration { @Bean - ExampleBean exampleBean() { - return new ExampleBean("test"); + ExampleBean<String> exampleBean() { + return new ExampleBean<>("test"); } } @@ -612,21 +719,21 @@ static class ImpliedOnBeanMethod { @Bean @ConditionalOnMissingBean - ExampleBean exampleBean2() { - return new ExampleBean("test"); + ExampleBean<String> exampleBean2() { + return new ExampleBean<>("test"); } } - static class ExampleFactoryBean implements FactoryBean<ExampleBean> { + static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> { ExampleFactoryBean(String value) { Assert.state(!value.contains("$"), "value should not contain '$'"); } @Override - public ExampleBean getObject() { - return new ExampleBean("fromFactory"); + public ExampleBean<String> getObject() { + return new ExampleBean<>("fromFactory"); } @Override @@ -648,8 +755,8 @@ static class NonspecificFactoryBean implements FactoryBean<Object> { } @Override - public ExampleBean getObject() { - return new ExampleBean("fromFactory"); + public ExampleBean<String> getObject() { + return new ExampleBean<>("fromFactory"); } @Override @@ -738,23 +845,91 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfig { + + @Bean + @ConditionalOnMissingBean(typeArguments = String.class) + ExampleBean<String> genericStringExampleBean() { + return new ExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentNamesConfig { + + @Bean + @ConditionalOnMissingBean(typeArgumentNames = "java.lang.String") + ExampleBean<String> genericStringNameExampleBean() { + return new ExampleBean<>("genericStringNameExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnMissingBean(typeArguments = Integer.class) + ExampleBean<Integer> genericIntegerExampleBean() { + return new ExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnMissingBean(value = ExampleBean.class, typeArguments = String.class) + ExampleBean<String> genericStringWithValueExampleBean() { + return new ExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class, + typeArguments = String.class) + TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig { + + @Bean + @ConditionalOnMissingBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class, + typeArguments = String.class) + TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + @TestAnnotation - static class ExampleBean { + static class ExampleBean<T> { - private String value; + private final T value; - ExampleBean(String value) { + ExampleBean(T value) { this.value = value; } @Override public String toString() { - return this.value; + return String.valueOf(this.value); } } - static class CustomExampleBean extends ExampleBean { + static class CustomExampleBean extends ExampleBean<String> { CustomExampleBean() { super("custom subclass"); @@ -762,7 +937,7 @@ static class CustomExampleBean extends ExampleBean { } - static class OtherExampleBean extends ExampleBean { + static class OtherExampleBean extends ExampleBean<String> { OtherExampleBean() { super("other subclass"); From 6f9af2a6894fc4bd338e176724670d915b5ec0e7 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Thu, 3 Feb 2022 19:19:02 +0200 Subject: [PATCH 2/6] Update OnBeanCondition to auto-detect generic type arguments --- .../condition/OnBeanCondition.java | 147 +++++++++--------- .../condition/ConditionalOnBeanTests.java | 28 ++-- .../ConditionalOnMissingBeanTests.java | 29 ++-- 3 files changed, 102 insertions(+), 102 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index f9585f007990..843c1c600f86 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -157,7 +157,6 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers(); - Set<Class<?>> typeArguments = spec.getTypeArguments(); if (spec.getStrategy() == SearchStrategy.ANCESTORS) { BeanFactory parent = beanFactory.getParentBeanFactory(); Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, @@ -165,11 +164,11 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s beanFactory = (ConfigurableListableBeanFactory) parent; } MatchResult result = new MatchResult(); - Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, + Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beanFactory, considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers); - for (String type : spec.getTypes()) { - Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, - parameterizedContainers, typeArguments); + for (ResolvableType type : spec.getTypes()) { + Collection<String> typeMatches = getBeanNamesForType(considerHierarchy, beanFactory, type, + parameterizedContainers); Iterator<String> iterator = typeMatches.iterator(); while (iterator.hasNext()) { String match = iterator.next(); @@ -178,10 +177,10 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s } } if (typeMatches.isEmpty()) { - result.recordUnmatchedType(type); + result.recordUnmatchedType(type.toString()); } else { - result.recordMatchedType(type, typeMatches); + result.recordMatchedType(type.toString(), typeMatches); } } for (String annotation : spec.getAnnotations()) { @@ -206,37 +205,37 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s return result; } - private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory, - boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) { + private Set<String> getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory, boolean considerHierarchy, + Set<Class<?>> ignoredTypes, Set<Class<?>> parameterizedContainers) { Set<String> result = null; - for (String ignoredType : ignoredTypes) { - Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, - ignoredType, parameterizedContainers, Collections.emptySet()); + for (Class<?> ignoredType : ignoredTypes) { + ResolvableType ignoredResolvableType = ResolvableType.forClass(ignoredType); + Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory, + ignoredResolvableType, parameterizedContainers); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } - private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, - ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers, - Set<Class<?>> typeArguments) throws LinkageError { + private Set<String> getBeanNamesForType(boolean considerHierarchy, + ListableBeanFactory beanFactory, ResolvableType type, Set<Class<?>> parameterizedContainers) + throws LinkageError { try { - return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader), - parameterizedContainers, typeArguments); + return getBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers); } - catch (ClassNotFoundException | NoClassDefFoundError ex) { + catch (NoClassDefFoundError ex) { return Collections.emptySet(); } } - private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type, - Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments) { + private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, + ResolvableType type, Set<Class<?>> parameterizedContainers) { Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers, - typeArguments, null); + null); return (result != null) ? result : Collections.emptySet(); } - private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory beanFactory, Class<?> type, + private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, ResolvableType type, Set<Class<?>> parameterizedContainers, Set<String> result) { result = addAll(result, beanFactory.getBeanNamesForType(type, true, false)); for (Class<?> container : parameterizedContainers) { @@ -247,36 +246,18 @@ private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory bea } private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, - Class<?> type, Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) { - if (!typeArguments.isEmpty()) { - result = collectBeanNamesWithTypeArguments(beanFactory, type, parameterizedContainers, typeArguments, - result); - } - else { - result = collectBeanNamesWithoutTypeArguments(beanFactory, type, parameterizedContainers, result); - } + ResolvableType type, Set<Class<?>> parameterizedContainers, Set<String> result) { + result = collectBeanNamesForType(beanFactory, type, parameterizedContainers, result); if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); if (parent instanceof ListableBeanFactory) { result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type, - parameterizedContainers, typeArguments, result); + parameterizedContainers, result); } } return result; } - private Set<String> collectBeanNamesWithTypeArguments(ListableBeanFactory beanFactory, Class<?> type, - Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) { - Class<?>[] typeArgumentArray = typeArguments.toArray(Class<?>[]::new); - ResolvableType genericType = ResolvableType.forClassWithGenerics(type, typeArgumentArray); - result = addAll(result, beanFactory.getBeanNamesForType(genericType, true, false)); - for (Class<?> container : parameterizedContainers) { - ResolvableType generic = ResolvableType.forClassWithGenerics(container, genericType); - result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); - } - return result; - } - private Set<String> getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError { Set<String> result = null; @@ -426,16 +407,14 @@ private static class Spec<A extends Annotation> { private final Set<String> names; - private final Set<String> types; + private final Set<ResolvableType> types; private final Set<String> annotations; - private final Set<String> ignoredTypes; + private final Set<Class<?>> ignoredTypes; private final Set<Class<?>> parameterizedContainers; - private final Set<Class<?>> typeArguments; - private final SearchStrategy strategy; Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations, @@ -448,11 +427,10 @@ private static class Spec<A extends Annotation> { this.annotationType = annotationType; this.names = extract(attributes, "name"); this.annotations = extract(attributes, "annotation"); - this.ignoredTypes = extract(attributes, "ignored", "ignoredType"); + this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType")); this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer")); - this.typeArguments = resolveWhenPossible(extract(attributes, "typeArguments", "typeArgumentNames")); this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null); - Set<String> types = extractTypes(attributes); + Set<ResolvableType> types = resolveTypes(extractTypes(attributes), extractTypeArguments(attributes)); BeanTypeDeductionException deductionException = null; if (types.isEmpty() && this.names.isEmpty()) { try { @@ -470,6 +448,10 @@ protected Set<String> extractTypes(MultiValueMap<String, Object> attributes) { return extract(attributes, "value", "type"); } + protected Set<String> extractTypeArguments(MultiValueMap<String, Object> attributes) { + return extract(attributes, "typeArguments", "typeArgumentNames"); + } + private Set<String> extract(MultiValueMap<String, Object> attributes, String... attributeNames) { if (attributes.isEmpty()) { return Collections.emptySet(); @@ -489,6 +471,31 @@ else if (value instanceof String) { return result.isEmpty() ? Collections.emptySet() : result; } + private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttributes) { + if (types.isEmpty()) { + return Collections.emptySet(); + } + Set<ResolvableType> resolved = new LinkedHashSet<>(types.size()); + Set<ResolvableType> resolvedTypeAttributes = resolveTypes(typeAttributes, Collections.emptySet()); + for (String type : types) { + try { + Class<?> typeClass = resolve(type, this.classLoader); + resolved.add(resolveType(typeClass, resolvedTypeAttributes)); + } + catch (ClassNotFoundException | NoClassDefFoundError ex) { + resolved.add(ResolvableType.NONE); + } + } + return resolved; + } + + private ResolvableType resolveType(Class<?> typeClass, Set<ResolvableType> typeAttributes) { + if (typeAttributes.isEmpty()) { + return ResolvableType.forClass(typeClass); + } + return ResolvableType.forClassWithGenerics(typeClass, typeAttributes.toArray(new ResolvableType[0])); + } + private void merge(Set<String> result, String... additional) { Collections.addAll(result, additional); } @@ -531,48 +538,52 @@ protected final String getAnnotationName() { return "@" + ClassUtils.getShortName(this.annotationType); } - private Set<String> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) { + private Set<ResolvableType> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) { if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) { return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata); } return Collections.emptySet(); } - private Set<String> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) { + private Set<ResolvableType> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) { try { - Class<?> returnType = getReturnType(context, metadata); - return Collections.singleton(returnType.getName()); + ResolvableType returnType = getReturnType(context, metadata); + return Collections.singleton(returnType); } catch (Throwable ex) { throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex); } } - private Class<?> getReturnType(ConditionContext context, MethodMetadata metadata) + private ResolvableType getReturnType(ConditionContext context, MethodMetadata metadata) throws ClassNotFoundException, LinkageError { // Safe to load at this point since we are in the REGISTER_BEAN phase ClassLoader classLoader = context.getClassLoader(); - Class<?> returnType = resolve(metadata.getReturnTypeName(), classLoader); + ResolvableType returnType = getMethodReturnType(metadata, classLoader); if (isParameterizedContainer(returnType)) { - returnType = getReturnTypeGeneric(metadata, classLoader); + returnType = returnType.getGeneric(); } return returnType; } - private boolean isParameterizedContainer(Class<?> type) { + private boolean isParameterizedContainer(ResolvableType type) { + Class<?> rawType = type.getRawClass(); + if (rawType == null) { + return false; + } for (Class<?> parameterizedContainer : this.parameterizedContainers) { - if (parameterizedContainer.isAssignableFrom(type)) { + if (parameterizedContainer.isAssignableFrom(rawType)) { return true; } } return false; } - private Class<?> getReturnTypeGeneric(MethodMetadata metadata, ClassLoader classLoader) + private ResolvableType getMethodReturnType(MethodMetadata metadata, ClassLoader classLoader) throws ClassNotFoundException, LinkageError { Class<?> declaringClass = resolve(metadata.getDeclaringClassName(), classLoader); Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName()); - return ResolvableType.forMethodReturnType(beanMethod).resolveGeneric(); + return ResolvableType.forMethodReturnType(beanMethod); } private Method findBeanMethod(Class<?> declaringClass, String methodName) { @@ -602,7 +613,7 @@ Set<String> getNames() { return this.names; } - Set<String> getTypes() { + Set<ResolvableType> getTypes() { return this.types; } @@ -610,14 +621,10 @@ Set<String> getAnnotations() { return this.annotations; } - Set<String> getIgnoredTypes() { + Set<Class<?>> getIgnoredTypes() { return this.ignoredTypes; } - Set<Class<?>> getTypeArguments() { - return this.typeArguments; - } - Set<Class<?>> getParameterizedContainers() { return this.parameterizedContainers; } @@ -635,7 +642,6 @@ public String toString() { boolean hasNames = !this.names.isEmpty(); boolean hasTypes = !this.types.isEmpty(); boolean hasIgnoredTypes = !this.ignoredTypes.isEmpty(); - boolean hasTypeArguments = !this.typeArguments.isEmpty(); StringBuilder string = new StringBuilder(); string.append("("); if (hasNames) { @@ -653,11 +659,6 @@ public String toString() { string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes)); string.append("; "); } - if (hasTypeArguments) { - string.append("argument types: "); - string.append(StringUtils.collectionToCommaDelimitedString(this.typeArguments)); - string.append("; "); - } string.append("SearchStrategy: "); string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH)); string.append(")"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index 453d3a94b467..af7fca1a0d9e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -592,24 +592,13 @@ ExampleBean<String> genericStringExampleBean() { static class GenericWithStringTypeArgumentsConfig { @Bean - @ConditionalOnBean(typeArguments = String.class) + @ConditionalOnBean ExampleBean<String> genericStringTypeArgumentsExampleBean() { return new ExampleBean<>("genericStringTypeArgumentsExampleBean"); } } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentNamesConfig { - - @Bean - @ConditionalOnBean(typeArgumentNames = "java.lang.String") - ExampleBean<String> genericStringNameExampleBean() { - return new ExampleBean<>("genericStringNameExampleBean"); - } - - } - @Configuration(proxyBeanMethods = false) static class GenericWithIntegerConfig { @@ -624,7 +613,7 @@ ExampleBean<Integer> genericIntegerExampleBean() { static class GenericWithIntegerTypeArgumentsConfig { @Bean - @ConditionalOnBean(typeArguments = Integer.class) + @ConditionalOnBean ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { return new ExampleBean<>(1_000); } @@ -642,11 +631,22 @@ ExampleBean<String> genericStringWithValueExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentNamesConfig { + + @Bean + @ConditionalOnBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String") + ExampleBean<String> genericStringNameExampleBean() { + return new ExampleBean<>("genericStringNameExampleBean"); + } + + } + @Configuration(proxyBeanMethods = false) static class TypeArgumentsConditionWithParameterizedContainerConfig { @Bean - @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class, typeArguments = String.class) + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { return new TestParameterizedContainer<>(); } 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 d1052e08d5bf..eff4d7c4d445 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 @@ -849,29 +849,18 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() { static class GenericWithStringTypeArgumentsConfig { @Bean - @ConditionalOnMissingBean(typeArguments = String.class) + @ConditionalOnMissingBean ExampleBean<String> genericStringExampleBean() { return new ExampleBean<>("genericStringExampleBean"); } } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentNamesConfig { - - @Bean - @ConditionalOnMissingBean(typeArgumentNames = "java.lang.String") - ExampleBean<String> genericStringNameExampleBean() { - return new ExampleBean<>("genericStringNameExampleBean"); - } - - } - @Configuration(proxyBeanMethods = false) static class GenericWithIntegerTypeArgumentsConfig { @Bean - @ConditionalOnMissingBean(typeArguments = Integer.class) + @ConditionalOnMissingBean ExampleBean<Integer> genericIntegerExampleBean() { return new ExampleBean<>(1_000); } @@ -889,12 +878,22 @@ ExampleBean<String> genericStringWithValueExampleBean() { } + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentNamesConfig { + + @Bean + @ConditionalOnMissingBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String") + ExampleBean<String> genericStringNameExampleBean() { + return new ExampleBean<>("genericStringNameExampleBean"); + } + + } + @Configuration(proxyBeanMethods = false) static class TypeArgumentsConditionWithParameterizedContainerConfig { @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class, - typeArguments = String.class) + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { return new TestParameterizedContainer<>(); } From 4ee1fd341c26ae5c330db5b7947a65d1733279e6 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Fri, 4 Feb 2022 01:14:06 +0200 Subject: [PATCH 3/6] Removed added annotation methods --- .../condition/ConditionalOnBean.java | 17 ----- .../condition/ConditionalOnMissingBean.java | 17 ----- .../condition/OnBeanCondition.java | 20 ++---- .../condition/ConditionalOnBeanTests.java | 66 +----------------- .../ConditionalOnMissingBeanTests.java | 67 +------------------ 5 files changed, 7 insertions(+), 180 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java index 66f1e3820fc2..7d5ca163a162 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java @@ -56,7 +56,6 @@ * another auto-configuration, make sure that the one using this condition runs after. * * @author Phillip Webb - * @author Uladzislau Seuruk * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @@ -111,20 +110,4 @@ */ Class<?>[] parameterizedContainer() default {}; - /** - * The class types of generic bean type arguments that should be checked. The - * condition matches when beans of all classes specified are contained in the - * {@link BeanFactory}. - * @return the class types of beans to check - */ - Class<?>[] typeArguments() default {}; - - /** - * The class type names of generic bean type arguments that should be checked. The - * condition matches when beans of all classes specified are contained in the - * {@link BeanFactory}. - * @return the class type names of beans to check - */ - String[] typeArgumentNames() default {}; - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java index d9dfaa122553..c08e6200c7bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java @@ -57,7 +57,6 @@ * * @author Phillip Webb * @author Andy Wilkinson - * @author Uladzislau Seuruk * @since 1.0.0 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @@ -127,20 +126,4 @@ */ Class<?>[] parameterizedContainer() default {}; - /** - * The class types of generic bean type arguments that should be checked. The - * condition matches when no bean of each class specified is contained in the - * {@link BeanFactory}. - * @return the class types of beans to check - */ - Class<?>[] typeArguments() default {}; - - /** - * The class type names of generic bean type arguments that should be checked. The - * condition matches when no bean of each class specified is contained in the - * {@link BeanFactory}. - * @return the class type names of beans to check - */ - String[] typeArgumentNames() default {}; - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index 843c1c600f86..84e7d053ecef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -430,7 +430,7 @@ private static class Spec<A extends Annotation> { this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType")); this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer")); this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null); - Set<ResolvableType> types = resolveTypes(extractTypes(attributes), extractTypeArguments(attributes)); + Set<ResolvableType> types = resolveTypes(extractTypes(attributes)); BeanTypeDeductionException deductionException = null; if (types.isEmpty() && this.names.isEmpty()) { try { @@ -448,10 +448,6 @@ protected Set<String> extractTypes(MultiValueMap<String, Object> attributes) { return extract(attributes, "value", "type"); } - protected Set<String> extractTypeArguments(MultiValueMap<String, Object> attributes) { - return extract(attributes, "typeArguments", "typeArgumentNames"); - } - private Set<String> extract(MultiValueMap<String, Object> attributes, String... attributeNames) { if (attributes.isEmpty()) { return Collections.emptySet(); @@ -471,16 +467,17 @@ else if (value instanceof String) { return result.isEmpty() ? Collections.emptySet() : result; } - private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttributes) { + private Set<ResolvableType> resolveTypes(Set<String> types) { if (types.isEmpty()) { return Collections.emptySet(); } Set<ResolvableType> resolved = new LinkedHashSet<>(types.size()); - Set<ResolvableType> resolvedTypeAttributes = resolveTypes(typeAttributes, Collections.emptySet()); for (String type : types) { try { Class<?> typeClass = resolve(type, this.classLoader); - resolved.add(resolveType(typeClass, resolvedTypeAttributes)); + ResolvableType resolvableType = typeClass.getTypeParameters().length != 0 + ? ResolvableType.forRawClass(typeClass) : ResolvableType.forClass(typeClass); + resolved.add(resolvableType); } catch (ClassNotFoundException | NoClassDefFoundError ex) { resolved.add(ResolvableType.NONE); @@ -489,13 +486,6 @@ private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttr return resolved; } - private ResolvableType resolveType(Class<?> typeClass, Set<ResolvableType> typeAttributes) { - if (typeAttributes.isEmpty()) { - return ResolvableType.forClass(typeClass); - } - return ResolvableType.forClassWithGenerics(typeClass, typeAttributes.toArray(new ResolvableType[0])); - } - private void merge(Set<String> result, String... additional) { Collections.addAll(result, additional); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index af7fca1a0d9e..4aba7aea5590 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -236,21 +236,6 @@ void genericWhenTypeArgumentMatches() { exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean"))); } - @Test - void genericWhenTypeArgumentNameNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerConfig.class, GenericWithStringTypeArgumentNamesConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); - } - - @Test - void genericWhenTypeArgumentNameMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, GenericWithStringTypeArgumentNamesConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericStringNameExampleBean"))); - } - @Test void genericWhenTypeArgumentWithValueMatches() { this.contextRunner @@ -293,32 +278,6 @@ void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean"))); } - @Test - void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); - } - - @Test - void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean", - "parameterizedContainerGenericWithValueExampleBean"))); - } - - @Test - void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean", - "parameterizedContainerGenericWithValueExampleBean"))); - } - private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -624,24 +583,13 @@ ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { static class TypeArgumentsConditionWithValueConfig { @Bean - @ConditionalOnBean(value = ExampleBean.class, typeArguments = String.class) + @ConditionalOnBean(value = ExampleBean.class) ExampleBean<String> genericStringWithValueExampleBean() { return new ExampleBean<>("genericStringWithValueExampleBean"); } } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentNamesConfig { - - @Bean - @ConditionalOnBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String") - ExampleBean<String> genericStringNameExampleBean() { - return new ExampleBean<>("genericStringNameExampleBean"); - } - - } - @Configuration(proxyBeanMethods = false) static class TypeArgumentsConditionWithParameterizedContainerConfig { @@ -653,18 +601,6 @@ TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExa } - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig { - - @Bean - @ConditionalOnBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class, - typeArguments = String.class) - TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - @TestAnnotation static class ExampleBean<T> { 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 eff4d7c4d445..ba7043e01d7b 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 @@ -365,23 +365,6 @@ void genericWhenSubclassTypeArgumentNotMatches() { .satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean"))); } - @Test - void genericWhenTypeArgumentNameNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, - GenericWithStringTypeArgumentNamesConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("genericIntegerExampleBean", "genericStringNameExampleBean"))); - } - - @Test - void genericWhenTypeArgumentNameMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - GenericWithStringTypeArgumentNamesConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - @Test void genericWhenTypeArgumentWithValueMatches() { this.contextRunner @@ -422,31 +405,6 @@ void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); } - @Test - void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean", - "parameterizedContainerGenericWithValueExampleBean"))); - } - - @Test - void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - - @Test - void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -871,24 +829,13 @@ ExampleBean<Integer> genericIntegerExampleBean() { static class TypeArgumentsConditionWithValueConfig { @Bean - @ConditionalOnMissingBean(value = ExampleBean.class, typeArguments = String.class) + @ConditionalOnMissingBean(value = ExampleBean.class) ExampleBean<String> genericStringWithValueExampleBean() { return new ExampleBean<>("genericStringWithValueExampleBean"); } } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentNamesConfig { - - @Bean - @ConditionalOnMissingBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String") - ExampleBean<String> genericStringNameExampleBean() { - return new ExampleBean<>("genericStringNameExampleBean"); - } - - } - @Configuration(proxyBeanMethods = false) static class TypeArgumentsConditionWithParameterizedContainerConfig { @@ -900,18 +847,6 @@ TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExa } - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig { - - @Bean - @ConditionalOnMissingBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class, - typeArguments = String.class) - TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - @TestAnnotation static class ExampleBean<T> { From c1c5b2ef2bd7d838d0a4e4fe19bf96e262885650 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Fri, 4 Feb 2022 01:49:09 +0200 Subject: [PATCH 4/6] Adjust styles --- .../boot/autoconfigure/condition/OnBeanCondition.java | 11 +++++------ .../condition/ConditionalOnBeanTests.java | 2 +- .../condition/ConditionalOnMissingBeanTests.java | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index 84e7d053ecef..1bf3d313ddfa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -210,16 +210,15 @@ private Set<String> getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory Set<String> result = null; for (Class<?> ignoredType : ignoredTypes) { ResolvableType ignoredResolvableType = ResolvableType.forClass(ignoredType); - Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory, - ignoredResolvableType, parameterizedContainers); + Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory, ignoredResolvableType, + parameterizedContainers); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } - private Set<String> getBeanNamesForType(boolean considerHierarchy, - ListableBeanFactory beanFactory, ResolvableType type, Set<Class<?>> parameterizedContainers) - throws LinkageError { + private Set<String> getBeanNamesForType(boolean considerHierarchy, ListableBeanFactory beanFactory, + ResolvableType type, Set<Class<?>> parameterizedContainers) throws LinkageError { try { return getBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers); } @@ -475,7 +474,7 @@ private Set<ResolvableType> resolveTypes(Set<String> types) { for (String type : types) { try { Class<?> typeClass = resolve(type, this.classLoader); - ResolvableType resolvableType = typeClass.getTypeParameters().length != 0 + ResolvableType resolvableType = (typeClass.getTypeParameters().length != 0) ? ResolvableType.forRawClass(typeClass) : ResolvableType.forClass(typeClass); resolved.add(resolvableType); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index 4aba7aea5590..f9a80b18e176 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -583,7 +583,7 @@ ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { static class TypeArgumentsConditionWithValueConfig { @Bean - @ConditionalOnBean(value = ExampleBean.class) + @ConditionalOnBean(ExampleBean.class) ExampleBean<String> genericStringWithValueExampleBean() { return new ExampleBean<>("genericStringWithValueExampleBean"); } 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 ba7043e01d7b..9317faacf905 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 @@ -829,7 +829,7 @@ ExampleBean<Integer> genericIntegerExampleBean() { static class TypeArgumentsConditionWithValueConfig { @Bean - @ConditionalOnMissingBean(value = ExampleBean.class) + @ConditionalOnMissingBean(ExampleBean.class) ExampleBean<String> genericStringWithValueExampleBean() { return new ExampleBean<>("genericStringWithValueExampleBean"); } From 1298a434dabae01338253896a90540895f3a4419 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Fri, 4 Feb 2022 11:41:28 +0200 Subject: [PATCH 5/6] Extract tests for generic return type to separate classes --- ...nditionalOnBeanGenericReturnTypeTests.java | 209 ++++++++++++++++++ .../condition/ConditionalOnBeanTests.java | 134 +---------- ...alOnMissingBeanGenericReturnTypeTests.java | 203 +++++++++++++++++ .../ConditionalOnMissingBeanTests.java | 159 +++---------- 4 files changed, 446 insertions(+), 259 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java new file mode 100644 index 000000000000..e57101e5f4cc --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2012-2020 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnBean @ConditionalOnBean} with generic bean return type. + * + * @author Uladzislau Seuruk + */ +class ConditionalOnBeanGenericReturnTypeTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void genericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class, + GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean", + "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies( + exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { + return (context) -> { + String[] beans = context.getBeanNamesForType(GenericExampleBean.class); + String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); + assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); + }; + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomConfig { + + @Bean + CustomExampleBean customExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringConfig { + + @Bean + GenericExampleBean<String> genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfig { + + @Bean + @ConditionalOnBean + GenericExampleBean<String> genericStringTypeArgumentsExampleBean() { + return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerConfig { + + @Bean + GenericExampleBean<Integer> genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnBean + GenericExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnBean(GenericExampleBean.class) + GenericExampleBean<String> genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer<GenericExampleBean<String>> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @TestAnnotation + static class GenericExampleBean<T> { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomExampleBean extends GenericExampleBean<String> { + + CustomExampleBean() { + super("custom subclass"); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface TestAnnotation { + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index f9a80b18e176..5bfb79e00d00 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -51,7 +51,6 @@ * * @author Dave Syer * @author Stephane Nicoll - * @author Uladzislau Seuruk */ class ConditionalOnBeanTests { @@ -227,57 +226,6 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); } - @Test - void genericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class, - GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean"))); - } - - @Test - void genericWhenTypeArgumentWithValueMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean"))); - } - - @Test - void genericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean", - "parameterizedContainerGenericExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean"))); - } - private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -415,11 +363,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, } - static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> { + static class ExampleFactoryBean implements FactoryBean<ExampleBean> { @Override - public ExampleBean<String> getObject() { - return new ExampleBean<>("fromFactory"); + public ExampleBean getObject() { + return new ExampleBean("fromFactory"); } @Override @@ -537,87 +485,23 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() { } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringConfig { - - @Bean - ExampleBean<String> genericStringExampleBean() { - return new ExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - ExampleBean<String> genericStringTypeArgumentsExampleBean() { - return new ExampleBean<>("genericStringTypeArgumentsExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerConfig { - - @Bean - ExampleBean<Integer> genericIntegerExampleBean() { - return new ExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() { - return new ExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnBean(ExampleBean.class) - ExampleBean<String> genericStringWithValueExampleBean() { - return new ExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - @TestAnnotation - static class ExampleBean<T> { + static class ExampleBean { - private final T value; + private String value; - ExampleBean(T value) { + ExampleBean(String value) { this.value = value; } @Override public String toString() { - return String.valueOf(this.value); + return this.value; } } - static class CustomExampleBean extends ExampleBean<String> { + static class CustomExampleBean extends ExampleBean { CustomExampleBean() { super("custom subclass"); @@ -625,7 +509,7 @@ static class CustomExampleBean extends ExampleBean<String> { } - static class OtherExampleBean extends ExampleBean<String> { + static class OtherExampleBean extends ExampleBean { OtherExampleBean() { super("other subclass"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java new file mode 100644 index 000000000000..31298c3b1e4d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012-2021 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean return type. + * + * @author Uladzislau Seuruk + */ +@SuppressWarnings("resource") +class ConditionalOnMissingBeanGenericReturnTypeTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void genericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class) + .run((context) -> assertThat(context) + .satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean", + "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomConfig.class, + TypeArgumentsConditionWithParameterizedContainerConfig.class) + .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + } + + private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { + return (context) -> { + String[] beans = context.getBeanNamesForType(GenericExampleBean.class); + String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); + assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); + }; + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomConfig { + + @Bean + CustomExampleBean customExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfig { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean<String> genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean<Integer> genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnMissingBean(GenericExampleBean.class) + GenericExampleBean<String> genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer<GenericExampleBean<String>> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @TestAnnotation + static class GenericExampleBean<T> { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomExampleBean extends GenericExampleBean<String> { + + CustomExampleBean() { + super("custom subclass"); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface TestAnnotation { + + } + +} 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 9317faacf905..f7087dd2331c 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 @@ -59,7 +59,6 @@ * @author Phillip Webb * @author Jakub Kubrynski * @author Andy Wilkinson - * @author Uladzislau Seuruk */ @SuppressWarnings("resource") class ConditionalOnMissingBeanTests { @@ -341,70 +340,6 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); } - @Test - void genericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean"))); - } - - @Test - void genericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - - @Test - void genericWhenSubclassTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean"))); - } - - @Test - void genericWhenTypeArgumentWithValueMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - - @Test - void genericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean", - "parameterizedContainerGenericExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -418,8 +353,8 @@ static class OnBeanInAncestorsConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.ANCESTORS) - ExampleBean<String> exampleBean2() { - return new ExampleBean<String>("test"); + ExampleBean exampleBean2() { + return new ExampleBean("test"); } } @@ -451,7 +386,7 @@ String bar() { static class FactoryBeanConfiguration { @Bean - FactoryBean<ExampleBean<String>> exampleBeanFactoryBean() { + FactoryBean<ExampleBean> exampleBeanFactoryBean() { return new ExampleFactoryBean("foo"); } @@ -477,7 +412,7 @@ static class ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration { static class FactoryBeanWithBeanMethodArgumentsConfiguration { @Bean - FactoryBean<ExampleBean<String>> exampleBeanFactoryBean(@Value("${theValue}") String value) { + FactoryBean<ExampleBean> exampleBeanFactoryBean(@Value("${theValue}") String value) { return new ExampleFactoryBean(value); } @@ -568,8 +503,8 @@ static class ConditionalOnFactoryBean { @Bean @ConditionalOnMissingBean(ExampleBean.class) - ExampleBean<String> createExampleBean() { - return new ExampleBean<>("direct"); + ExampleBean createExampleBean() { + return new ExampleBean("direct"); } } @@ -579,8 +514,8 @@ static class ConditionalOnIgnoredSubclass { @Bean @ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class) - ExampleBean<String> exampleBean() { - return new ExampleBean<>("test"); + ExampleBean exampleBean() { + return new ExampleBean("test"); } } @@ -591,8 +526,8 @@ static class ConditionalOnIgnoredSubclassByName { @Bean @ConditionalOnMissingBean(value = ExampleBean.class, ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean") - ExampleBean<String> exampleBean() { - return new ExampleBean<>("test"); + ExampleBean exampleBean() { + return new ExampleBean("test"); } } @@ -666,8 +601,8 @@ String bar() { static class ExampleBeanConfiguration { @Bean - ExampleBean<String> exampleBean() { - return new ExampleBean<>("test"); + ExampleBean exampleBean() { + return new ExampleBean("test"); } } @@ -677,21 +612,21 @@ static class ImpliedOnBeanMethod { @Bean @ConditionalOnMissingBean - ExampleBean<String> exampleBean2() { - return new ExampleBean<>("test"); + ExampleBean exampleBean2() { + return new ExampleBean("test"); } } - static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> { + static class ExampleFactoryBean implements FactoryBean<ExampleBean> { ExampleFactoryBean(String value) { Assert.state(!value.contains("$"), "value should not contain '$'"); } @Override - public ExampleBean<String> getObject() { - return new ExampleBean<>("fromFactory"); + public ExampleBean getObject() { + return new ExampleBean("fromFactory"); } @Override @@ -713,8 +648,8 @@ static class NonspecificFactoryBean implements FactoryBean<Object> { } @Override - public ExampleBean<String> getObject() { - return new ExampleBean<>("fromFactory"); + public ExampleBean getObject() { + return new ExampleBean("fromFactory"); } @Override @@ -803,67 +738,23 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() { } - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentsConfig { - - @Bean - @ConditionalOnMissingBean - ExampleBean<String> genericStringExampleBean() { - return new ExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnMissingBean - ExampleBean<Integer> genericIntegerExampleBean() { - return new ExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnMissingBean(ExampleBean.class) - ExampleBean<String> genericStringWithValueExampleBean() { - return new ExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - @TestAnnotation - static class ExampleBean<T> { + static class ExampleBean { - private final T value; + private String value; - ExampleBean(T value) { + ExampleBean(String value) { this.value = value; } @Override public String toString() { - return String.valueOf(this.value); + return this.value; } } - static class CustomExampleBean extends ExampleBean<String> { + static class CustomExampleBean extends ExampleBean { CustomExampleBean() { super("custom subclass"); @@ -871,7 +762,7 @@ static class CustomExampleBean extends ExampleBean<String> { } - static class OtherExampleBean extends ExampleBean<String> { + static class OtherExampleBean extends ExampleBean { OtherExampleBean() { super("other subclass"); From bc2db920a97acbef1b7f17e7314ee93c82fbd03a Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk <vladislavsevruk@gmail.com> Date: Fri, 4 Feb 2022 12:09:00 +0200 Subject: [PATCH 6/6] Adjust styles --- .../ConditionalOnMissingBeanGenericReturnTypeTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java index 31298c3b1e4d..d9b5178b7879 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java @@ -34,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean return type. + * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean + * return type. * * @author Uladzislau Seuruk */