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 f136d0b1a0b..dbbd850369a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; /** @@ -66,22 +68,31 @@ public @interface ConditionalOnBean { /** * The class types of beans that should be checked. The condition matches when beans - * of all classes specified are contained in the {@link BeanFactory}. + * of all classes specified are contained in the {@link BeanFactory}. Beans that are + * not autowire candidates are ignored. * @return the class types of beans to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ Class[] value() default {}; /** * The class type names of beans that should be checked. The condition matches when - * beans of all classes specified are contained in the {@link BeanFactory}. + * beans of all classes specified are contained in the {@link BeanFactory}. Beans that + * are not autowire candidates are ignored. * @return the class type names of beans to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ String[] type() default {}; /** * The annotation type decorating a bean that should be checked. The condition matches * when all the annotations specified are defined on beans in the {@link BeanFactory}. + * Beans that are not autowire candidates are ignored. * @return the class-level annotation types to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ Class[] annotation() 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 c08e6200c7b..60f721f2411 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; /** @@ -67,15 +69,21 @@ public @interface ConditionalOnMissingBean { /** * The class types of beans that should be checked. The condition matches when no bean - * of each class specified is contained in the {@link BeanFactory}. + * of each class specified is contained in the {@link BeanFactory}. Beans that are not + * autowire candidates are ignored. * @return the class types of beans to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ Class[] value() default {}; /** * The class type names of beans that should be checked. The condition matches when no - * bean of each class specified is contained in the {@link BeanFactory}. + * bean of each class specified is contained in the {@link BeanFactory}. Beans that + * are not autowire candidates are ignored. * @return the class type names of beans to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ String[] type() default {}; @@ -97,8 +105,10 @@ public @interface ConditionalOnMissingBean { /** * The annotation type decorating a bean that should be checked. The condition matches * when each annotation specified is missing from all beans in the - * {@link BeanFactory}. + * {@link BeanFactory}. Beans that are not autowire candidates are ignored. * @return the class-level annotation types to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ Class[] annotation() default {}; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java index c1b79e7af14..43866f3ceb9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; /** @@ -51,22 +53,28 @@ public @interface ConditionalOnSingleCandidate { /** * The class type of bean that should be checked. The condition matches if a bean of * the class specified is contained in the {@link BeanFactory} and a primary candidate - * exists in case of multiple instances. + * exists in case of multiple instances. Beans that are not autowire candidates are + * ignored. *

* This attribute may not be used in conjunction with * {@link #type()}, but it may be used instead of {@link #type()}. * @return the class type of the bean to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ Class value() default Object.class; /** * The class type name of bean that should be checked. The condition matches if a bean * of the class specified is contained in the {@link BeanFactory} and a primary - * candidate exists in case of multiple instances. + * candidate exists in case of multiple instances. Beans that are not autowire + * candidates are ignored. *

* This attribute may not be used in conjunction with * {@link #value()}, but it may be used instead of {@link #value()}. * @return the class type name of the bean to check + * @see Bean#autowireCandidate() + * @see BeanDefinition#isAutowireCandidate */ String type() 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 521bbc7fb35..14478d14eea 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 @@ -24,18 +24,21 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.BiPredicate; import java.util.function.Predicate; import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.SingletonBeanRegistry; @@ -211,26 +214,30 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat Set beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, spec.getIgnoredTypes(), parameterizedContainers); for (String type : spec.getTypes()) { - Collection typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, - parameterizedContainers); - typeMatches - .removeIf((match) -> beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)); - if (typeMatches.isEmpty()) { + Map typeMatchedDefinitions = getBeanDefinitionsForType(classLoader, + considerHierarchy, beanFactory, type, parameterizedContainers); + Set typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions, + (name, definition) -> !beansIgnoredByType.contains(name) && !ScopedProxyUtils.isScopedTarget(name) + && (definition == null || definition.isAutowireCandidate())); + if (typeMatchedNames.isEmpty()) { result.recordUnmatchedType(type); } else { - result.recordMatchedType(type, typeMatches); + result.recordMatchedType(type, typeMatchedNames); } } for (String annotation : spec.getAnnotations()) { - Set annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation, - considerHierarchy); - annotationMatches.removeAll(beansIgnoredByType); - if (annotationMatches.isEmpty()) { + Map annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader, + beanFactory, annotation, considerHierarchy); + Set annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions, + (name, definition) -> !beansIgnoredByType.contains(name) + && (definition == null || definition.isAutowireCandidate())); + if (annotationMatchedNames.isEmpty()) { result.recordUnmatchedAnnotation(annotation); } else { - result.recordMatchedAnnotation(annotation, annotationMatches); + result.recordMatchedAnnotation(annotation, annotationMatchedNames); + } } for (String beanName : spec.getNames()) { @@ -244,63 +251,76 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return result; } + private Set matchedNamesFrom(Map namedDefinitions, + BiPredicate filter) { + Set matchedNames = new LinkedHashSet<>(namedDefinitions.size()); + for (Entry namedDefinition : namedDefinitions.entrySet()) { + if (filter.test(namedDefinition.getKey(), namedDefinition.getValue())) { + matchedNames.add(namedDefinition.getKey()); + } + } + return matchedNames; + } + private Set getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory, boolean considerHierarchy, Set ignoredTypes, Set> parameterizedContainers) { Set result = null; for (String ignoredType : ignoredTypes) { - Collection ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, - ignoredType, parameterizedContainers); + Collection ignoredNames = getBeanDefinitionsForType(classLoader, considerHierarchy, beanFactory, + ignoredType, parameterizedContainers) + .keySet(); result = addAll(result, ignoredNames); } return (result != null) ? result : Collections.emptySet(); } - private Set getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, + private Map getBeanDefinitionsForType(ClassLoader classLoader, boolean considerHierarchy, ListableBeanFactory beanFactory, String type, Set> parameterizedContainers) throws LinkageError { try { - return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader), + return getBeanDefinitionsForType(beanFactory, considerHierarchy, resolve(type, classLoader), parameterizedContainers); } catch (ClassNotFoundException | NoClassDefFoundError ex) { - return Collections.emptySet(); + return Collections.emptyMap(); } } - private Set getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class type, - Set> parameterizedContainers) { - Set result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers, - null); - return (result != null) ? result : Collections.emptySet(); + private Map getBeanDefinitionsForType(ListableBeanFactory beanFactory, + boolean considerHierarchy, Class type, Set> parameterizedContainers) { + Map result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type, + parameterizedContainers, null); + return (result != null) ? result : Collections.emptyMap(); } - private Set collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, - Class type, Set> parameterizedContainers, Set result) { - result = addAll(result, beanFactory.getBeanNamesForType(type, true, false)); + private Map collectBeanDefinitionsForType(ListableBeanFactory beanFactory, + boolean considerHierarchy, Class type, Set> parameterizedContainers, + Map result) { + result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory); for (Class container : parameterizedContainers) { ResolvableType generic = ResolvableType.forClassWithGenerics(container, type); - result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); + result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory); } if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) { BeanFactory parent = hierarchicalBeanFactory.getParentBeanFactory(); if (parent instanceof ListableBeanFactory listableBeanFactory) { - result = collectBeanNamesForType(listableBeanFactory, considerHierarchy, type, parameterizedContainers, - result); + result = collectBeanDefinitionsForType(listableBeanFactory, considerHierarchy, type, + parameterizedContainers, result); } } return result; } - private Set getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory, - String type, boolean considerHierarchy) throws LinkageError { - Set result = null; + private Map getBeanDefinitionsForAnnotation(ClassLoader classLoader, + ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError { + Map result = null; try { - result = collectBeanNamesForAnnotation(beanFactory, resolveAnnotationType(classLoader, type), + result = collectBeanDefinitionsForAnnotation(beanFactory, resolveAnnotationType(classLoader, type), considerHierarchy, result); } catch (ClassNotFoundException ex) { // Continue } - return (result != null) ? result : Collections.emptySet(); + return (result != null) ? result : Collections.emptyMap(); } @SuppressWarnings("unchecked") @@ -309,13 +329,14 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return (Class) resolve(type, classLoader); } - private Set collectBeanNamesForAnnotation(ListableBeanFactory beanFactory, - Class annotationType, boolean considerHierarchy, Set result) { - result = addAll(result, getBeanNamesForAnnotation(beanFactory, annotationType)); + private Map collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory, + Class annotationType, boolean considerHierarchy, Map result) { + result = putAll(result, getBeanNamesForAnnotation(beanFactory, annotationType), beanFactory); if (considerHierarchy) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); if (parent instanceof ListableBeanFactory listableBeanFactory) { - result = collectBeanNamesForAnnotation(listableBeanFactory, annotationType, considerHierarchy, result); + result = collectBeanDefinitionsForAnnotation(listableBeanFactory, annotationType, considerHierarchy, + result); } } return result; @@ -453,12 +474,27 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat return result; } - private static Set addAll(Set result, String[] additional) { - if (ObjectUtils.isEmpty(additional)) { + private static Map putAll(Map result, String[] beanNames, + ListableBeanFactory beanFactory) { + if (ObjectUtils.isEmpty(beanNames)) { return result; } - result = (result != null) ? result : new LinkedHashSet<>(); - Collections.addAll(result, additional); + if (result == null) { + result = new LinkedHashMap<>(); + } + for (String beanName : beanNames) { + if (beanFactory instanceof ConfigurableListableBeanFactory clbf) { + try { + result.put(beanName, clbf.getBeanDefinition(beanName)); + } + catch (NoSuchBeanDefinitionException ex) { + result.put(beanName, null); + } + } + else { + result.put(beanName, null); + } + } return result; } 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 06a49b1e618..aa6729914d4 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-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,6 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportResource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -242,6 +241,26 @@ class ConditionalOnBeanTests { .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); } + @Test + void conditionalOnBeanTypeIgnoresNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanClassConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void conditionalOnBeanNameMatchesNotAutowireCandidateBean() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void conditionalOnAnnotatedBeanIgnoresNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + private Consumer exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -273,7 +292,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(annotation = EnableScheduling.class) + @ConditionalOnBean(annotation = TestAnnotation.class) static class OnAnnotationConfiguration { @Bean @@ -317,7 +336,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - @EnableScheduling + @TestAnnotation static class FooConfiguration { @Bean @@ -327,6 +346,16 @@ class ConditionalOnBeanTests { } + @Configuration(proxyBeanMethods = false) + static class NotAutowireCandidateConfiguration { + + @Bean(autowireCandidate = false) + String foo() { + return "foo"; + } + + } + @Configuration(proxyBeanMethods = false) @ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml") static class XmlConfiguration { @@ -530,6 +559,16 @@ class ConditionalOnBeanTests { } + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + @TestAnnotation static class ExampleBean { 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 9ca96314e9f..5374d9dca96 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-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,6 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportResource; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -345,6 +344,25 @@ class ConditionalOnMissingBeanTests { .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); } + @Test + void typeBasedMatchingIgnoresBeanThatIsNotAutowireCandidate() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanTypeConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + + @Test + void nameBasedMatchingConsidersBeanThatIsNotAutowireCandidate() { + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanNameConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean("bar")); + } + + @Test + void annotationBasedMatchingIgnoresBeanThatIsNotAutowireCandidateBean() { + this.contextRunner + .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .run((context) -> assertThat(context).hasBean("bar")); + } + private Consumer exampleBeanRequirement(String... names) { return (context) -> { String[] beans = context.getBeanNamesForType(ExampleBean.class); @@ -375,6 +393,17 @@ class ConditionalOnMissingBeanTests { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(type = "java.lang.String") + static class OnBeanTypeConfiguration { + + @Bean + String bar() { + return "bar"; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo", value = Date.class) @ConditionalOnBean(name = "foo", value = Date.class) @@ -536,7 +565,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingBean(annotation = EnableScheduling.class) + @ConditionalOnMissingBean(annotation = TestAnnotation.class) static class OnAnnotationConfiguration { @Bean @@ -558,7 +587,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - @EnableScheduling + @TestAnnotation static class FooConfiguration { @Bean @@ -568,6 +597,16 @@ class ConditionalOnMissingBeanTests { } + @Configuration(proxyBeanMethods = false) + static class NotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + String foo() { + return "foo"; + } + + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo") static class HierarchyConsidered { @@ -731,6 +770,16 @@ class ConditionalOnMissingBeanTests { } + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfig { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + @TestAnnotation static class ExampleBean { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java index 7431fa2a33c..2c268d75bf1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java @@ -173,6 +173,17 @@ class ConditionalOnSingleCandidateTests { })); } + @Test + void singleCandidateMultipleCandidatesOneAutowireCandidate() { + this.contextRunner + .withUserConfiguration(AlphaConfiguration.class, BravoNonAutowireConfiguration.class, + OnBeanSingleCandidateConfiguration.class) + .run((context) -> { + assertThat(context).hasBean("consumer"); + assertThat(context.getBean("consumer")).isEqualTo("alpha"); + }); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(String.class) static class OnBeanSingleCandidateConfiguration { @@ -282,4 +293,14 @@ class ConditionalOnSingleCandidateTests { } + @Configuration(proxyBeanMethods = false) + static class BravoNonAutowireConfiguration { + + @Bean(autowireCandidate = false) + String bravo() { + return "bravo"; + } + + } + }