From aa7864ea38ec759ab60c049150f22d7dc3648ac3 Mon Sep 17 00:00:00 2001 From: Uladzislau Seuruk Date: Thu, 20 Jan 2022 13:21:45 +0200 Subject: [PATCH 1/2] Add ConditionalOnBean support for generic @Bean return types Update `ConditionalOnBean` so support generics when resolving the `@Bean` method. See gh-29500 Co-authored-by: Phillip Webb --- .../condition/OnBeanCondition.java | 107 ++++----- ...nditionalOnBeanGenericReturnTypeTests.java | 209 ++++++++++++++++++ ...alOnMissingBeanGenericReturnTypeTests.java | 204 +++++++++++++++++ 3 files changed, 460 insertions(+), 60 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/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 2c9c9229eb..607fe64784 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 @@ -76,6 +76,7 @@ import org.springframework.util.StringUtils; * @author Jakub Kubrynski * @author Stephane Nicoll * @author Andy Wilkinson + * @author Uladzislau Seuruk * @see ConditionalOnBean * @see ConditionalOnMissingBean * @see ConditionalOnSingleCandidate @@ -205,13 +206,13 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat ConfigurableListableBeanFactory beanFactory = getSearchBeanFactory(spec); ClassLoader classLoader = spec.getContext().getClassLoader(); boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; - Set> parameterizedContainers = spec.getParameterizedContainers(); + Set parameterizedContainers = spec.getParameterizedContainers(); MatchResult result = new MatchResult(); - Set beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, + Set beansIgnoredByType = getNamesOfBeansIgnoredByType(beanFactory, considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers); - for (String type : spec.getTypes()) { - Map typeMatchedDefinitions = getBeanDefinitionsForType(classLoader, - considerHierarchy, beanFactory, type, parameterizedContainers); + for (ResolvableType type : spec.getTypes()) { + Map typeMatchedDefinitions = getBeanDefinitionsForType(beanFactory, + considerHierarchy, type, parameterizedContainers); Set typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions, (name, definition) -> !ScopedProxyUtils.isScopedTarget(name) && isCandidate(beanFactory, name, definition, beansIgnoredByType)); @@ -296,42 +297,31 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return true; } - private Set getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory, - boolean considerHierarchy, Set ignoredTypes, Set> parameterizedContainers) { + private Set getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory, boolean considerHierarchy, + Set ignoredTypes, Set parameterizedContainers) { Set result = null; - for (String ignoredType : ignoredTypes) { - Collection ignoredNames = getBeanDefinitionsForType(classLoader, considerHierarchy, beanFactory, - ignoredType, parameterizedContainers) + for (ResolvableType ignoredType : ignoredTypes) { + Collection ignoredNames = getBeanDefinitionsForType(beanFactory, considerHierarchy, ignoredType, + parameterizedContainers) .keySet(); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } - private Map getBeanDefinitionsForType(ClassLoader classLoader, boolean considerHierarchy, - ListableBeanFactory beanFactory, String type, Set> parameterizedContainers) throws LinkageError { - try { - return getBeanDefinitionsForType(beanFactory, considerHierarchy, resolve(type, classLoader), - parameterizedContainers); - } - catch (ClassNotFoundException | NoClassDefFoundError ex) { - return Collections.emptyMap(); - } - } - private Map getBeanDefinitionsForType(ListableBeanFactory beanFactory, - boolean considerHierarchy, Class type, Set> parameterizedContainers) { + boolean considerHierarchy, ResolvableType type, Set parameterizedContainers) { Map result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type, parameterizedContainers, null); return (result != null) ? result : Collections.emptyMap(); } private Map collectBeanDefinitionsForType(ListableBeanFactory beanFactory, - boolean considerHierarchy, Class type, Set> parameterizedContainers, + boolean considerHierarchy, ResolvableType type, Set parameterizedContainers, Map result) { result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory); - for (Class container : parameterizedContainers) { - ResolvableType generic = ResolvableType.forClassWithGenerics(container, type); + for (ResolvableType parameterizedContainer : parameterizedContainers) { + ResolvableType generic = ResolvableType.forClassWithGenerics(parameterizedContainer.resolve(), type); result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory); } if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) { @@ -550,13 +540,13 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat private final Set names; - private final Set types; + private final Set types; private final Set annotations; - private final Set ignoredTypes; + private final Set ignoredTypes; - private final Set> parameterizedContainers; + private final Set parameterizedContainers; private final SearchStrategy strategy; @@ -570,10 +560,10 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat 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.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null); - Set types = extractTypes(attributes); + Set types = resolveWhenPossible(extractTypes(attributes)); BeanTypeDeductionException deductionException = null; if (types.isEmpty() && this.names.isEmpty() && this.annotations.isEmpty()) { try { @@ -614,17 +604,18 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat Collections.addAll(result, additional); } - private Set> resolveWhenPossible(Set classNames) { + private Set resolveWhenPossible(Set classNames) { if (classNames.isEmpty()) { return Collections.emptySet(); } - Set> resolved = new LinkedHashSet<>(classNames.size()); + Set resolved = new LinkedHashSet<>(classNames.size()); for (String className : classNames) { try { - resolved.add(resolve(className, this.context.getClassLoader())); + Class type = resolve(className, this.context.getClassLoader()); + resolved.add(ResolvableType.forRawClass(type)); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - // Ignore + resolved.add(ResolvableType.NONE); } } return resolved; @@ -653,48 +644,44 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return "@" + ClassUtils.getShortName(this.annotationType); } - private Set deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) { + private Set deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) { if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) { return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata); } return Collections.emptySet(); } - private Set deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) { + private Set deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) { try { - Class returnType = getReturnType(context, metadata); - return Collections.singleton(returnType.getName()); + return Set.of(getReturnType(context, metadata)); } 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); - if (isParameterizedContainer(returnType)) { - returnType = getReturnTypeGeneric(metadata, classLoader); + ResolvableType returnType = getMethodReturnType(metadata, classLoader); + if (isParameterizedContainer(returnType.resolve())) { + returnType = returnType.getGeneric(); } return returnType; } private boolean isParameterizedContainer(Class type) { - for (Class parameterizedContainer : this.parameterizedContainers) { - if (parameterizedContainer.isAssignableFrom(type)) { - return true; - } - } - return false; + return (type != null) && this.parameterizedContainers.stream() + .map(ResolvableType::resolve) + .anyMatch((container) -> container != null && container.isAssignableFrom(type)); } - 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) { @@ -720,6 +707,10 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return (this.strategy != null) ? this.strategy : SearchStrategy.ALL; } + Set getTypes() { + return this.types; + } + private ConditionContext getContext() { return this.context; } @@ -728,19 +719,15 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return this.names; } - protected Set getTypes() { - return this.types; - } - private Set getAnnotations() { return this.annotations; } - private Set getIgnoredTypes() { + private Set getIgnoredTypes() { return this.ignoredTypes; } - private Set> getParameterizedContainers() { + private Set getParameterizedContainers() { return this.parameterizedContainers; } @@ -847,13 +834,13 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat this.unmatchedAnnotations.add(annotation); } - private void recordMatchedType(String type, Collection matchingNames) { - this.matchedTypes.put(type, matchingNames); + private void recordMatchedType(ResolvableType type, Collection matchingNames) { + this.matchedTypes.put(type.toString(), matchingNames); this.namesOfAllMatches.addAll(matchingNames); } - private void recordUnmatchedType(String type) { - this.unmatchedTypes.add(type); + private void recordUnmatchedType(ResolvableType type) { + this.unmatchedTypes.add(type.toString()); } boolean isAllMatched() { 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 0000000000..ba3c649cac --- /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-2025 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 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 genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfig { + + @Bean + @ConditionalOnBean + GenericExampleBean genericStringTypeArgumentsExampleBean() { + return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerConfig { + + @Bean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnBean + GenericExampleBean genericIntegerTypeArgumentsExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomExampleBean extends GenericExampleBean { + + 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/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java new file mode 100644 index 0000000000..3f60bd24e3 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java @@ -0,0 +1,204 @@ +/* + * Copyright 2012-2025 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 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 genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfig { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfig { + + @Bean + @ConditionalOnMissingBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfig { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomExampleBean extends GenericExampleBean { + + CustomExampleBean() { + super("custom subclass"); + } + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface TestAnnotation { + + } + +} From 3911ca7c8e0399d6408b959d982e0bbc5677d635 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 28 Jan 2025 19:26:48 -0800 Subject: [PATCH 2/2] Polish @ConditionalOnBean and @ConditionalOnMissingBean tests Combine generic and regular tests and apply some polish. See gh-29500 --- ...nditionalOnBeanGenericReturnTypeTests.java | 209 -------- .../condition/ConditionalOnBeanTests.java | 291 ++++++++--- ...alOnMissingBeanGenericReturnTypeTests.java | 204 -------- .../ConditionalOnMissingBeanTests.java | 475 ++++++++++++------ 4 files changed, 546 insertions(+), 633 deletions(-) delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java delete 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 deleted file mode 100644 index ba3c649cac..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2012-2025 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 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 genericStringExampleBean() { - return new GenericExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - GenericExampleBean genericStringTypeArgumentsExampleBean() { - return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerConfig { - - @Bean - GenericExampleBean genericIntegerExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - GenericExampleBean genericIntegerTypeArgumentsExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnBean(GenericExampleBean.class) - GenericExampleBean genericStringWithValueExampleBean() { - return new GenericExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @TestAnnotation - static class GenericExampleBean { - - private final T value; - - GenericExampleBean(T value) { - this.value = value; - } - - @Override - public String toString() { - return String.valueOf(this.value); - } - - } - - static class CustomExampleBean extends GenericExampleBean { - - 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 01d852c0cd..53d9eba239 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Dave Syer * @author Stephane Nicoll + * @author Uladzislau Seuruk */ class ConditionalOnBeanTests { @@ -116,8 +117,8 @@ class ConditionalOnBeanTests { @Test void withPropertyPlaceholderClassName() { this.contextRunner - .withUserConfiguration(PropertySourcesPlaceholderConfigurer.class, WithPropertyPlaceholderClassName.class, - OnBeanClassConfiguration.class) + .withUserConfiguration(PropertySourcesPlaceholderConfigurer.class, + WithPropertyPlaceholderClassNameConfiguration.class, OnBeanClassConfiguration.class) .withPropertyValues("mybeanclass=java.lang.String") .run((context) -> assertThat(context).hasNotFailed()); } @@ -165,7 +166,8 @@ class ConditionalOnBeanTests { @Test void conditionEvaluationConsidersChangeInTypeWhenBeanIsOverridden() { this.contextRunner.withAllowBeanDefinitionOverriding(true) - .withUserConfiguration(OriginalDefinition.class, OverridingDefinition.class, ConsumingConfiguration.class) + .withUserConfiguration(OriginalDefinitionConfiguration.class, OverridingDefinitionConfiguration.class, + ConsumingConfiguration.class) .run((context) -> { assertThat(context).hasBean("testBean"); assertThat(context).hasSingleBean(Integer.class); @@ -176,69 +178,73 @@ class ConditionalOnBeanTests { @Test void parameterizedContainerWhenValueIsOfMissingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("otherExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "otherExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfMissingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("otherExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "otherExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test @@ -257,7 +263,7 @@ class ConditionalOnBeanTests { @Test void conditionalOnAnnotatedBeanIgnoresNotAutowireCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotAutowireCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @@ -284,13 +290,66 @@ class ConditionalOnBeanTests { @Test void conditionalOnAnnotatedBeanIgnoresNotDefaultCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotDefaultCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } - private Consumer exampleBeanRequirement(String... names) { + @Test + void genericWhenTypeArgumentMatches() { + this.contextRunner.withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithStringTypeArgumentsConfiguration.class, GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericStringTypeArgumentsExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericIntegerExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + private Consumer beansAndContainersNamed(Class type, String... names) { return (context) -> { - String[] beans = context.getBeanNamesForType(ExampleBean.class); + String[] beans = context.getBeanNamesForType(type); String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); }; @@ -429,7 +488,7 @@ class ConditionalOnBeanTests { @Configuration(proxyBeanMethods = false) @Import(WithPropertyPlaceholderClassNameRegistrar.class) - static class WithPropertyPlaceholderClassName { + static class WithPropertyPlaceholderClassNameConfiguration { } @@ -495,27 +554,8 @@ class ConditionalOnBeanTests { } - static class ExampleFactoryBean implements FactoryBean { - - @Override - public ExampleBean getObject() { - return new ExampleBean("fromFactory"); - } - - @Override - public Class getObjectType() { - return ExampleBean.class; - } - - @Override - public boolean isSingleton() { - return false; - } - - } - @Configuration(proxyBeanMethods = false) - static class OriginalDefinition { + static class OriginalDefinitionConfiguration { @Bean String testBean() { @@ -526,7 +566,7 @@ class ConditionalOnBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnBean(String.class) - static class OverridingDefinition { + static class OverridingDefinitionConfiguration { @Bean Integer testBean() { @@ -545,7 +585,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { + static class ParameterizedWithCustomConfiguration { @Bean CustomExampleBean customExampleBean() { @@ -555,7 +595,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomConfig { + static class ParameterizedWithoutCustomConfiguration { @Bean OtherExampleBean otherExampleBean() { @@ -565,7 +605,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomContainerConfig { + static class ParameterizedWithoutCustomContainerConfiguration { @Bean TestParameterizedContainer otherExampleBean() { @@ -575,7 +615,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomContainerConfig { + static class ParameterizedWithCustomContainerConfiguration { @Bean TestParameterizedContainer customExampleBean() { @@ -585,7 +625,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithValueConfig { + static class ParameterizedConditionWithValueConfiguration { @Bean @ConditionalOnBean(value = CustomExampleBean.class, parameterizedContainer = TestParameterizedContainer.class) @@ -596,7 +636,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnTypeConfig { + static class ParameterizedConditionWithReturnTypeConfiguration { @Bean @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) @@ -607,7 +647,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnRegistrationTypeConfig { + static class ParameterizedConditionWithReturnRegistrationTypeConfiguration { @Bean @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) @@ -618,7 +658,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class AnnotatedNotAutowireCandidateConfig { + static class AnnotatedNotAutowireCandidateConfiguration { @Bean(autowireCandidate = false) ExampleBean exampleBean() { @@ -628,7 +668,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class AnnotatedNotDefaultCandidateConfig { + static class AnnotatedNotDefaultCandidateConfiguration { @Bean(defaultCandidate = false) ExampleBean exampleBean() { @@ -637,6 +677,99 @@ class ConditionalOnBeanTests { } + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomGenericConfiguration { + + @Bean + CustomGenericExampleBean customGenericExampleBean() { + return new CustomGenericExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringConfiguration { + + @Bean + GenericExampleBean genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfiguration { + + @Bean + @ConditionalOnBean + GenericExampleBean genericStringTypeArgumentsExampleBean() { + return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerConfiguration { + + @Bean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfiguration { + + @Bean + @ConditionalOnBean + GenericExampleBean genericIntegerTypeArgumentsExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfiguration { + + @Bean + @ConditionalOnBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfiguration { + + @Bean + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + static class ExampleFactoryBean implements FactoryBean { + + @Override + public ExampleBean getObject() { + return new ExampleBean("fromFactory"); + } + + @Override + public Class getObjectType() { + return ExampleBean.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + @TestAnnotation static class ExampleBean { @@ -669,6 +802,30 @@ class ConditionalOnBeanTests { } + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomGenericExampleBean extends GenericExampleBean { + + CustomGenericExampleBean() { + super("custom subclass"); + } + + } + @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented 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 deleted file mode 100644 index 3f60bd24e3..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2012-2025 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 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 genericStringExampleBean() { - return new GenericExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnMissingBean - GenericExampleBean genericIntegerExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnMissingBean(GenericExampleBean.class) - GenericExampleBean genericStringWithValueExampleBean() { - return new GenericExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @TestAnnotation - static class GenericExampleBean { - - private final T value; - - GenericExampleBean(T value) { - this.value = value; - } - - @Override - public String toString() { - return String.valueOf(this.value); - } - - } - - static class CustomExampleBean extends GenericExampleBean { - - 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 b908dc67a0..7b5e44da3e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -61,6 +61,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Jakub Kubrynski * @author Andy Wilkinson + * @author Uladzislau Seuruk */ @SuppressWarnings("resource") class ConditionalOnMissingBeanTests { @@ -100,7 +101,7 @@ class ConditionalOnMissingBeanTests { void hierarchyConsidered() { this.contextRunner.withUserConfiguration(FooConfiguration.class) .run((parent) -> new ApplicationContextRunner().withParent(parent) - .withUserConfiguration(HierarchyConsidered.class) + .withUserConfiguration(HierarchyConsideredConfiguration.class) .run((context) -> assertThat(context.containsLocalBean("bar")).isFalse())); } @@ -108,13 +109,13 @@ class ConditionalOnMissingBeanTests { void hierarchyNotConsidered() { this.contextRunner.withUserConfiguration(FooConfiguration.class) .run((parent) -> new ApplicationContextRunner().withParent(parent) - .withUserConfiguration(HierarchyNotConsidered.class) + .withUserConfiguration(HierarchyNotConsideredConfiguration.class) .run((context) -> assertThat(context.containsLocalBean("bar")).isTrue())); } @Test void impliedOnBeanMethod() { - this.contextRunner.withUserConfiguration(ExampleBeanConfiguration.class, ImpliedOnBeanMethod.class) + this.contextRunner.withUserConfiguration(ExampleBeanConfiguration.class, ImpliedOnBeanMethodConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(ExampleBean.class)); } @@ -173,7 +174,8 @@ class ConditionalOnMissingBeanTests { @Test void testOnMissingBeanConditionWithFactoryBean() { this.contextRunner - .withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnMissingBeanProducedByFactoryBean.class, + .withUserConfiguration(FactoryBeanConfiguration.class, + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -182,7 +184,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithComponentScannedFactoryBean() { this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @@ -190,7 +193,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArguments() { this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @@ -198,7 +202,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() { this.contextRunner .withUserConfiguration(FactoryBeanWithBeanMethodArgumentsConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .withPropertyValues("theValue=foo") .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -207,7 +212,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithConcreteFactoryBean() { this.contextRunner .withUserConfiguration(ConcreteFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -216,7 +222,8 @@ class ConditionalOnMissingBeanTests { // We could not tell that the FactoryBean would ultimately create an ExampleBean this.contextRunner .withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context).getBeans(ExampleBean.class).hasSize(2)); } @@ -224,7 +231,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithRegisteredFactoryBean() { this.contextRunner .withUserConfiguration(RegisteredFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -232,7 +240,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithNonspecificFactoryBeanWithClassAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanClassAttributeConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -240,7 +249,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithNonspecificFactoryBeanWithStringAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanStringAttributeConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -248,15 +258,16 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithFactoryBeanInXml() { this.contextRunner .withUserConfiguration(FactoryBeanXmlConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test void testOnMissingBeanConditionWithIgnoredSubclass() { this.contextRunner - .withUserConfiguration(CustomExampleBeanConfiguration.class, ConditionalOnIgnoredSubclass.class, - PropertyPlaceholderAutoConfiguration.class) + .withUserConfiguration(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclassConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> { assertThat(context).getBeans(ExampleBean.class).hasSize(2); assertThat(context).getBeans(CustomExampleBean.class).hasSize(1); @@ -266,8 +277,8 @@ class ConditionalOnMissingBeanTests { @Test void testOnMissingBeanConditionWithIgnoredSubclassByName() { this.contextRunner - .withUserConfiguration(CustomExampleBeanConfiguration.class, ConditionalOnIgnoredSubclassByName.class, - PropertyPlaceholderAutoConfiguration.class) + .withUserConfiguration(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclassByNameConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> { assertThat(context).getBeans(ExampleBean.class).hasSize(2); assertThat(context).getBeans(CustomExampleBean.class).hasSize(1); @@ -304,89 +315,97 @@ class ConditionalOnMissingBeanTests { @Test void parameterizedContainerWhenValueIsOfMissingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("otherExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "otherExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfMissingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("otherExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "otherExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void typeBasedMatchingIgnoresBeanThatIsNotAutowireCandidate() { - this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanTypeConfiguration.class) + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanTypeConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @Test void nameBasedMatchingConsidersBeanThatIsNotAutowireCandidate() { - this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanNameConfiguration.class) + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanNameConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @Test void annotationBasedMatchingIgnoresBeanThatIsNotAutowireCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotAutowireCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @Test void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() { - this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanTypeConfiguration.class) + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanTypeConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @@ -394,27 +413,99 @@ class ConditionalOnMissingBeanTests { void typeBasedMatchingIgnoresFactoryBeanThatIsNotDefaultCandidate() { this.contextRunner .withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class, - ConditionalOnMissingFactoryBean.class) + ConditionalOnMissingFactoryBeanConfiguration.class) .run((context) -> assertThat(context).hasBean("&exampleFactoryBean") .hasBean("&additionalExampleFactoryBean")); } @Test void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() { - this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class) + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @Test void annotationBasedMatchingIgnoresBeanThatIsNotDefaultCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotDefaultCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } - private Consumer exampleBeanRequirement(String... names) { + @Test + void genericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithStringTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericStringExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericIntegerExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericStringExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + private Consumer beansAndContainersNamed(Class type, String... names) { return (context) -> { - String[] beans = context.getBeanNamesForType(ExampleBean.class); + String[] beans = context.getBeanNamesForType(type); String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); }; @@ -584,7 +675,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnMissingBeanProducedByFactoryBean { + static class ConditionalOnMissingBeanProducedByFactoryBeanConfiguration { @Bean @ConditionalOnMissingBean @@ -595,7 +686,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnMissingFactoryBean { + static class ConditionalOnMissingFactoryBeanConfiguration { @Bean @ConditionalOnMissingBean @@ -606,7 +697,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnIgnoredSubclass { + static class ConditionalOnIgnoredSubclassConfiguration { @Bean @ConditionalOnMissingBean(ignored = CustomExampleBean.class) @@ -617,7 +708,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnIgnoredSubclassByName { + static class ConditionalOnIgnoredSubclassByNameConfiguration { @Bean @ConditionalOnMissingBean( @@ -683,7 +774,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class NotAutowireCandidateConfig { + static class NotAutowireCandidateConfiguration { @Bean(autowireCandidate = false) String foo() { @@ -693,7 +784,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class NotDefaultCandidateConfig { + static class NotDefaultCandidateConfiguration { @Bean(defaultCandidate = false) String foo() { @@ -704,7 +795,7 @@ class ConditionalOnMissingBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo") - static class HierarchyConsidered { + static class HierarchyConsideredConfiguration { @Bean String bar() { @@ -715,7 +806,7 @@ class ConditionalOnMissingBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo", search = SearchStrategy.CURRENT) - static class HierarchyNotConsidered { + static class HierarchyNotConsideredConfiguration { @Bean String bar() { @@ -751,7 +842,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ImpliedOnBeanMethod { + static class ImpliedOnBeanMethodConfiguration { @Bean @ConditionalOnMissingBean @@ -761,6 +852,153 @@ class ConditionalOnMissingBeanTests { } + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomConfiguration { + + @Bean + CustomExampleBean customExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithoutCustomConfiguration { + + @Bean + OtherExampleBean otherExampleBean() { + return new OtherExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithoutCustomContainerConfiguration { + + @Bean + TestParameterizedContainer otherExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomContainerConfiguration { + + @Bean + TestParameterizedContainer customExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithValueConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + CustomExampleBean conditionalCustomExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithReturnTypeConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + CustomExampleBean conditionalCustomExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithReturnRegistrationTypeConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer conditionalCustomExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfiguration { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotDefaultCandidateConfiguration { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomGenericConfiguration { + + @Bean + CustomGenericExampleBean customGenericExampleBean() { + return new CustomGenericExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfiguration { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfiguration { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfiguration { + + @Bean + @ConditionalOnMissingBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + static class ExampleFactoryBean implements FactoryBean { ExampleFactoryBean(String value) { @@ -807,99 +1045,6 @@ class ConditionalOnMissingBeanTests { } - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { - - @Bean - CustomExampleBean customExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomConfig { - - @Bean - OtherExampleBean otherExampleBean() { - return new OtherExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomContainerConfig { - - @Bean - TestParameterizedContainer otherExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomContainerConfig { - - @Bean - TestParameterizedContainer customExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithValueConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - CustomExampleBean conditionalCustomExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnTypeConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - CustomExampleBean conditionalCustomExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnRegistrationTypeConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer conditionalCustomExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class AnnotatedNotAutowireCandidateConfig { - - @Bean(autowireCandidate = false) - ExampleBean exampleBean() { - return new ExampleBean("value"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class AnnotatedNotDefaultCandidateConfig { - - @Bean(autowireCandidate = false) - ExampleBean exampleBean() { - return new ExampleBean("value"); - } - - } - @TestAnnotation static class ExampleBean { @@ -956,6 +1101,30 @@ class ConditionalOnMissingBeanTests { } + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomGenericExampleBean extends GenericExampleBean { + + CustomGenericExampleBean() { + super("custom subclass"); + } + + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented