Skip to content

Commit 2ecb4eb

Browse files
committed
Ignore non-default candidates in type-based matching
Closes gh-22403
1 parent 8183c5b commit 2ecb4eb

File tree

18 files changed

+271
-179
lines changed

18 files changed

+271
-179
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.beans.factory.BeanFactory;
2727
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2829
import org.springframework.context.annotation.Bean;
2930
import org.springframework.context.annotation.Conditional;
3031

@@ -69,30 +70,37 @@
6970
/**
7071
* The class types of beans that should be checked. The condition matches when beans
7172
* of all classes specified are contained in the {@link BeanFactory}. Beans that are
72-
* not autowire candidates are ignored.
73+
* not autowire candidates or that are not default candidates are ignored.
7374
* @return the class types of beans to check
7475
* @see Bean#autowireCandidate()
7576
* @see BeanDefinition#isAutowireCandidate
77+
* @see Bean#defaultCandidate()
78+
* @see AbstractBeanDefinition#isDefaultCandidate
7679
*/
7780
Class<?>[] value() default {};
7881

7982
/**
8083
* The class type names of beans that should be checked. The condition matches when
8184
* beans of all classes specified are contained in the {@link BeanFactory}. Beans that
82-
* are not autowire candidates are ignored.
85+
* are not autowire candidates or that are not default candidates are ignored.
8386
* @return the class type names of beans to check
8487
* @see Bean#autowireCandidate()
8588
* @see BeanDefinition#isAutowireCandidate
89+
* @see Bean#defaultCandidate()
90+
* @see AbstractBeanDefinition#isDefaultCandidate
8691
*/
8792
String[] type() default {};
8893

8994
/**
9095
* The annotation type decorating a bean that should be checked. The condition matches
9196
* when all the annotations specified are defined on beans in the {@link BeanFactory}.
92-
* Beans that are not autowire candidates are ignored.
97+
* Beans that are not autowire candidates or that are not default candidates are
98+
* ignored.
9399
* @return the class-level annotation types to check
94100
* @see Bean#autowireCandidate()
95101
* @see BeanDefinition#isAutowireCandidate
102+
* @see Bean#defaultCandidate()
103+
* @see AbstractBeanDefinition#isDefaultCandidate
96104
*/
97105
Class<? extends Annotation>[] annotation() default {};
98106

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.beans.factory.BeanFactory;
2727
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2829
import org.springframework.context.annotation.Bean;
2930
import org.springframework.context.annotation.Conditional;
3031

@@ -70,20 +71,24 @@
7071
/**
7172
* The class types of beans that should be checked. The condition matches when no bean
7273
* of each class specified is contained in the {@link BeanFactory}. Beans that are not
73-
* autowire candidates are ignored.
74+
* autowire candidates or that are not default candidates are ignored.
7475
* @return the class types of beans to check
7576
* @see Bean#autowireCandidate()
7677
* @see BeanDefinition#isAutowireCandidate
78+
* @see Bean#defaultCandidate()
79+
* @see AbstractBeanDefinition#isDefaultCandidate
7780
*/
7881
Class<?>[] value() default {};
7982

8083
/**
8184
* The class type names of beans that should be checked. The condition matches when no
8285
* bean of each class specified is contained in the {@link BeanFactory}. Beans that
83-
* are not autowire candidates are ignored.
86+
* are not autowire candidates or that are not default candidates are ignored.
8487
* @return the class type names of beans to check
8588
* @see Bean#autowireCandidate()
8689
* @see BeanDefinition#isAutowireCandidate
90+
* @see Bean#defaultCandidate()
91+
* @see AbstractBeanDefinition#isDefaultCandidate
8792
*/
8893
String[] type() default {};
8994

@@ -105,10 +110,13 @@
105110
/**
106111
* The annotation type decorating a bean that should be checked. The condition matches
107112
* when each annotation specified is missing from all beans in the
108-
* {@link BeanFactory}. Beans that are not autowire candidates are ignored.
113+
* {@link BeanFactory}. Beans that are not autowire candidates or that are not default
114+
* candidates are ignored.
109115
* @return the class-level annotation types to check
110116
* @see Bean#autowireCandidate()
111117
* @see BeanDefinition#isAutowireCandidate
118+
* @see Bean#defaultCandidate()
119+
* @see AbstractBeanDefinition#isDefaultCandidate
112120
*/
113121
Class<? extends Annotation>[] annotation() default {};
114122

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.beans.factory.BeanFactory;
2626
import org.springframework.beans.factory.config.BeanDefinition;
27+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2728
import org.springframework.context.annotation.Bean;
2829
import org.springframework.context.annotation.Conditional;
2930

@@ -53,28 +54,32 @@
5354
/**
5455
* The class type of bean that should be checked. The condition matches if a bean of
5556
* the class specified is contained in the {@link BeanFactory} and a primary candidate
56-
* exists in case of multiple instances. Beans that are not autowire candidates are
57-
* ignored.
57+
* exists in case of multiple instances. Beans that are not autowire candidates or
58+
* that are not default candidates are ignored.
5859
* <p>
5960
* This attribute may <strong>not</strong> be used in conjunction with
6061
* {@link #type()}, but it may be used instead of {@link #type()}.
6162
* @return the class type of the bean to check
6263
* @see Bean#autowireCandidate()
6364
* @see BeanDefinition#isAutowireCandidate
65+
* @see Bean#defaultCandidate()
66+
* @see AbstractBeanDefinition#isDefaultCandidate
6467
*/
6568
Class<?> value() default Object.class;
6669

6770
/**
6871
* The class type name of bean that should be checked. The condition matches if a bean
6972
* of the class specified is contained in the {@link BeanFactory} and a primary
7073
* candidate exists in case of multiple instances. Beans that are not autowire
71-
* candidates are ignored.
74+
* candidates or that are not default candidates are ignored.
7275
* <p>
7376
* This attribute may <strong>not</strong> be used in conjunction with
7477
* {@link #value()}, but it may be used instead of {@link #value()}.
7578
* @return the class type name of the bean to check
7679
* @see Bean#autowireCandidate()
7780
* @see BeanDefinition#isAutowireCandidate
81+
* @see Bean#defaultCandidate()
82+
* @see AbstractBeanDefinition#isDefaultCandidate
7883
*/
7984
String type() default "";
8085

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.beans.factory.config.BeanDefinition;
4343
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4444
import org.springframework.beans.factory.config.SingletonBeanRegistry;
45+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4546
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
4647
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
4748
import org.springframework.context.annotation.Bean;
@@ -217,8 +218,8 @@ protected final MatchResult getMatchingBeans(Spec<?> spec) {
217218
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
218219
considerHierarchy, beanFactory, type, parameterizedContainers);
219220
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
220-
(name, definition) -> !beansIgnoredByType.contains(name) && !ScopedProxyUtils.isScopedTarget(name)
221-
&& (definition == null || definition.isAutowireCandidate()));
221+
(name, definition) -> isCandidate(name, definition, beansIgnoredByType)
222+
&& !ScopedProxyUtils.isScopedTarget(name));
222223
if (typeMatchedNames.isEmpty()) {
223224
result.recordUnmatchedType(type);
224225
}
@@ -230,8 +231,7 @@ protected final MatchResult getMatchingBeans(Spec<?> spec) {
230231
Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader,
231232
beanFactory, annotation, considerHierarchy);
232233
Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions,
233-
(name, definition) -> !beansIgnoredByType.contains(name)
234-
&& (definition == null || definition.isAutowireCandidate()));
234+
(name, definition) -> isCandidate(name, definition, beansIgnoredByType));
235235
if (annotationMatchedNames.isEmpty()) {
236236
result.recordUnmatchedAnnotation(annotation);
237237
}
@@ -262,6 +262,18 @@ private Set<String> matchedNamesFrom(Map<String, BeanDefinition> namedDefinition
262262
return matchedNames;
263263
}
264264

265+
private boolean isCandidate(String name, BeanDefinition definition, Set<String> ignoredBeans) {
266+
return (!ignoredBeans.contains(name))
267+
&& (definition == null || (definition.isAutowireCandidate() && isDefaultCandidate(definition)));
268+
}
269+
270+
private boolean isDefaultCandidate(BeanDefinition definition) {
271+
if (definition instanceof AbstractBeanDefinition abstractBeanDefinition) {
272+
return abstractBeanDefinition.isDefaultCandidate();
273+
}
274+
return true;
275+
}
276+
265277
private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,
266278
boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {
267279
Set<String> result = null;

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,25 @@ void conditionalOnAnnotatedBeanIgnoresNotAutowireCandidateBean() {
261261
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
262262
}
263263

264+
@Test
265+
void conditionalOnBeanTypeIgnoresNotDefaultCandidateBean() {
266+
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanClassConfiguration.class)
267+
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
268+
}
269+
270+
@Test
271+
void conditionalOnBeanNameMatchesNotDefaultCandidateBean() {
272+
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class)
273+
.run((context) -> assertThat(context).hasBean("bar"));
274+
}
275+
276+
@Test
277+
void conditionalOnAnnotatedBeanIgnoresNotDefaultCandidateBean() {
278+
this.contextRunner
279+
.withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class)
280+
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
281+
}
282+
264283
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
265284
return (context) -> {
266285
String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -356,6 +375,16 @@ String foo() {
356375

357376
}
358377

378+
@Configuration(proxyBeanMethods = false)
379+
static class NotDefaultCandidateConfiguration {
380+
381+
@Bean(defaultCandidate = false)
382+
String foo() {
383+
return "foo";
384+
}
385+
386+
}
387+
359388
@Configuration(proxyBeanMethods = false)
360389
@ImportResource("org/springframework/boot/autoconfigure/condition/foo.xml")
361390
static class XmlConfiguration {
@@ -569,6 +598,16 @@ ExampleBean exampleBean() {
569598

570599
}
571600

601+
@Configuration(proxyBeanMethods = false)
602+
static class AnnotatedNotDefaultCandidateConfig {
603+
604+
@Bean(defaultCandidate = false)
605+
ExampleBean exampleBean() {
606+
return new ExampleBean("value");
607+
}
608+
609+
}
610+
572611
@TestAnnotation
573612
static class ExampleBean {
574613

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,25 @@ void annotationBasedMatchingIgnoresBeanThatIsNotAutowireCandidateBean() {
363363
.run((context) -> assertThat(context).hasBean("bar"));
364364
}
365365

366+
@Test
367+
void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() {
368+
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanTypeConfiguration.class)
369+
.run((context) -> assertThat(context).hasBean("bar"));
370+
}
371+
372+
@Test
373+
void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() {
374+
this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class)
375+
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
376+
}
377+
378+
@Test
379+
void annotationBasedMatchingIgnoresBeanThatIsNotDefaultCandidateBean() {
380+
this.contextRunner
381+
.withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class)
382+
.run((context) -> assertThat(context).hasBean("bar"));
383+
}
384+
366385
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
367386
return (context) -> {
368387
String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -607,6 +626,16 @@ String foo() {
607626

608627
}
609628

629+
@Configuration(proxyBeanMethods = false)
630+
static class NotDefaultCandidateConfig {
631+
632+
@Bean(defaultCandidate = false)
633+
String foo() {
634+
return "foo";
635+
}
636+
637+
}
638+
610639
@Configuration(proxyBeanMethods = false)
611640
@ConditionalOnMissingBean(name = "foo")
612641
static class HierarchyConsidered {
@@ -780,6 +809,16 @@ ExampleBean exampleBean() {
780809

781810
}
782811

812+
@Configuration(proxyBeanMethods = false)
813+
static class AnnotatedNotDefaultCandidateConfig {
814+
815+
@Bean(autowireCandidate = false)
816+
ExampleBean exampleBean() {
817+
return new ExampleBean("value");
818+
}
819+
820+
}
821+
783822
@TestAnnotation
784823
static class ExampleBean {
785824

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidateTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@ void singleCandidateMultipleCandidatesOneAutowireCandidate() {
184184
});
185185
}
186186

187+
@Test
188+
void singleCandidateMultipleCandidatesOneDefaultCandidate() {
189+
this.contextRunner
190+
.withUserConfiguration(AlphaConfiguration.class, BravoNonDefaultConfiguration.class,
191+
OnBeanSingleCandidateConfiguration.class)
192+
.run((context) -> {
193+
assertThat(context).hasBean("consumer");
194+
assertThat(context.getBean("consumer")).isEqualTo("alpha");
195+
});
196+
}
197+
187198
@Configuration(proxyBeanMethods = false)
188199
@ConditionalOnSingleCandidate(String.class)
189200
static class OnBeanSingleCandidateConfiguration {
@@ -303,4 +314,14 @@ String bravo() {
303314

304315
}
305316

317+
@Configuration(proxyBeanMethods = false)
318+
static class BravoNonDefaultConfiguration {
319+
320+
@Bean(defaultCandidate = false)
321+
String bravo() {
322+
return "bravo";
323+
}
324+
325+
}
326+
306327
}

0 commit comments

Comments
 (0)