Skip to content

Commit 8183c5b

Browse files
committed
Ignore non-autowire candidates in type-based matching
Closes gh-41526
1 parent 25c7695 commit 8183c5b

File tree

7 files changed

+232
-58
lines changed

7 files changed

+232
-58
lines changed

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

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,8 @@
2424
import java.lang.annotation.Target;
2525

2626
import org.springframework.beans.factory.BeanFactory;
27+
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.context.annotation.Bean;
2729
import org.springframework.context.annotation.Conditional;
2830

2931
/**
@@ -66,22 +68,31 @@
6668

6769
/**
6870
* The class types of beans that should be checked. The condition matches when beans
69-
* of all classes specified are contained in the {@link BeanFactory}.
71+
* of all classes specified are contained in the {@link BeanFactory}. Beans that are
72+
* not autowire candidates are ignored.
7073
* @return the class types of beans to check
74+
* @see Bean#autowireCandidate()
75+
* @see BeanDefinition#isAutowireCandidate
7176
*/
7277
Class<?>[] value() default {};
7378

7479
/**
7580
* The class type names of beans that should be checked. The condition matches when
76-
* beans of all classes specified are contained in the {@link BeanFactory}.
81+
* beans of all classes specified are contained in the {@link BeanFactory}. Beans that
82+
* are not autowire candidates are ignored.
7783
* @return the class type names of beans to check
84+
* @see Bean#autowireCandidate()
85+
* @see BeanDefinition#isAutowireCandidate
7886
*/
7987
String[] type() default {};
8088

8189
/**
8290
* The annotation type decorating a bean that should be checked. The condition matches
8391
* when all the annotations specified are defined on beans in the {@link BeanFactory}.
92+
* Beans that are not autowire candidates are ignored.
8493
* @return the class-level annotation types to check
94+
* @see Bean#autowireCandidate()
95+
* @see BeanDefinition#isAutowireCandidate
8596
*/
8697
Class<? extends Annotation>[] annotation() default {};
8798

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

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,8 @@
2424
import java.lang.annotation.Target;
2525

2626
import org.springframework.beans.factory.BeanFactory;
27+
import org.springframework.beans.factory.config.BeanDefinition;
28+
import org.springframework.context.annotation.Bean;
2729
import org.springframework.context.annotation.Conditional;
2830

2931
/**
@@ -67,15 +69,21 @@
6769

6870
/**
6971
* The class types of beans that should be checked. The condition matches when no bean
70-
* of each class specified is contained in the {@link BeanFactory}.
72+
* of each class specified is contained in the {@link BeanFactory}. Beans that are not
73+
* autowire candidates are ignored.
7174
* @return the class types of beans to check
75+
* @see Bean#autowireCandidate()
76+
* @see BeanDefinition#isAutowireCandidate
7277
*/
7378
Class<?>[] value() default {};
7479

7580
/**
7681
* The class type names of beans that should be checked. The condition matches when no
77-
* bean of each class specified is contained in the {@link BeanFactory}.
82+
* bean of each class specified is contained in the {@link BeanFactory}. Beans that
83+
* are not autowire candidates are ignored.
7884
* @return the class type names of beans to check
85+
* @see Bean#autowireCandidate()
86+
* @see BeanDefinition#isAutowireCandidate
7987
*/
8088
String[] type() default {};
8189

@@ -97,8 +105,10 @@
97105
/**
98106
* The annotation type decorating a bean that should be checked. The condition matches
99107
* when each annotation specified is missing from all beans in the
100-
* {@link BeanFactory}.
108+
* {@link BeanFactory}. Beans that are not autowire candidates are ignored.
101109
* @return the class-level annotation types to check
110+
* @see Bean#autowireCandidate()
111+
* @see BeanDefinition#isAutowireCandidate
102112
*/
103113
Class<? extends Annotation>[] annotation() default {};
104114

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
2323
import java.lang.annotation.Target;
2424

2525
import org.springframework.beans.factory.BeanFactory;
26+
import org.springframework.beans.factory.config.BeanDefinition;
27+
import org.springframework.context.annotation.Bean;
2628
import org.springframework.context.annotation.Conditional;
2729

2830
/**
@@ -51,22 +53,28 @@
5153
/**
5254
* The class type of bean that should be checked. The condition matches if a bean of
5355
* the class specified is contained in the {@link BeanFactory} and a primary candidate
54-
* exists in case of multiple instances.
56+
* exists in case of multiple instances. Beans that are not autowire candidates are
57+
* ignored.
5558
* <p>
5659
* This attribute may <strong>not</strong> be used in conjunction with
5760
* {@link #type()}, but it may be used instead of {@link #type()}.
5861
* @return the class type of the bean to check
62+
* @see Bean#autowireCandidate()
63+
* @see BeanDefinition#isAutowireCandidate
5964
*/
6065
Class<?> value() default Object.class;
6166

6267
/**
6368
* The class type name of bean that should be checked. The condition matches if a bean
6469
* of the class specified is contained in the {@link BeanFactory} and a primary
65-
* candidate exists in case of multiple instances.
70+
* candidate exists in case of multiple instances. Beans that are not autowire
71+
* candidates are ignored.
6672
* <p>
6773
* This attribute may <strong>not</strong> be used in conjunction with
6874
* {@link #value()}, but it may be used instead of {@link #value()}.
6975
* @return the class type name of the bean to check
76+
* @see Bean#autowireCandidate()
77+
* @see BeanDefinition#isAutowireCandidate
7078
*/
7179
String type() default "";
7280

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

+76-40
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,21 @@
2424
import java.util.Collections;
2525
import java.util.HashMap;
2626
import java.util.HashSet;
27+
import java.util.LinkedHashMap;
2728
import java.util.LinkedHashSet;
2829
import java.util.List;
2930
import java.util.Locale;
3031
import java.util.Map;
3132
import java.util.Map.Entry;
3233
import java.util.Set;
34+
import java.util.function.BiPredicate;
3335
import java.util.function.Predicate;
3436

3537
import org.springframework.aop.scope.ScopedProxyUtils;
3638
import org.springframework.beans.factory.BeanFactory;
3739
import org.springframework.beans.factory.HierarchicalBeanFactory;
3840
import org.springframework.beans.factory.ListableBeanFactory;
41+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3942
import org.springframework.beans.factory.config.BeanDefinition;
4043
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4144
import org.springframework.beans.factory.config.SingletonBeanRegistry;
@@ -211,26 +214,30 @@ protected final MatchResult getMatchingBeans(Spec<?> spec) {
211214
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
212215
spec.getIgnoredTypes(), parameterizedContainers);
213216
for (String type : spec.getTypes()) {
214-
Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
215-
parameterizedContainers);
216-
typeMatches
217-
.removeIf((match) -> beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match));
218-
if (typeMatches.isEmpty()) {
217+
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
218+
considerHierarchy, beanFactory, type, parameterizedContainers);
219+
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
220+
(name, definition) -> !beansIgnoredByType.contains(name) && !ScopedProxyUtils.isScopedTarget(name)
221+
&& (definition == null || definition.isAutowireCandidate()));
222+
if (typeMatchedNames.isEmpty()) {
219223
result.recordUnmatchedType(type);
220224
}
221225
else {
222-
result.recordMatchedType(type, typeMatches);
226+
result.recordMatchedType(type, typeMatchedNames);
223227
}
224228
}
225229
for (String annotation : spec.getAnnotations()) {
226-
Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
227-
considerHierarchy);
228-
annotationMatches.removeAll(beansIgnoredByType);
229-
if (annotationMatches.isEmpty()) {
230+
Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader,
231+
beanFactory, annotation, considerHierarchy);
232+
Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions,
233+
(name, definition) -> !beansIgnoredByType.contains(name)
234+
&& (definition == null || definition.isAutowireCandidate()));
235+
if (annotationMatchedNames.isEmpty()) {
230236
result.recordUnmatchedAnnotation(annotation);
231237
}
232238
else {
233-
result.recordMatchedAnnotation(annotation, annotationMatches);
239+
result.recordMatchedAnnotation(annotation, annotationMatchedNames);
240+
234241
}
235242
}
236243
for (String beanName : spec.getNames()) {
@@ -244,63 +251,76 @@ protected final MatchResult getMatchingBeans(Spec<?> spec) {
244251
return result;
245252
}
246253

254+
private Set<String> matchedNamesFrom(Map<String, BeanDefinition> namedDefinitions,
255+
BiPredicate<String, BeanDefinition> filter) {
256+
Set<String> matchedNames = new LinkedHashSet<>(namedDefinitions.size());
257+
for (Entry<String, BeanDefinition> namedDefinition : namedDefinitions.entrySet()) {
258+
if (filter.test(namedDefinition.getKey(), namedDefinition.getValue())) {
259+
matchedNames.add(namedDefinition.getKey());
260+
}
261+
}
262+
return matchedNames;
263+
}
264+
247265
private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,
248266
boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {
249267
Set<String> result = null;
250268
for (String ignoredType : ignoredTypes) {
251-
Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory,
252-
ignoredType, parameterizedContainers);
269+
Collection<String> ignoredNames = getBeanDefinitionsForType(classLoader, considerHierarchy, beanFactory,
270+
ignoredType, parameterizedContainers)
271+
.keySet();
253272
result = addAll(result, ignoredNames);
254273
}
255274
return (result != null) ? result : Collections.emptySet();
256275
}
257276

258-
private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy,
277+
private Map<String, BeanDefinition> getBeanDefinitionsForType(ClassLoader classLoader, boolean considerHierarchy,
259278
ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError {
260279
try {
261-
return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader),
280+
return getBeanDefinitionsForType(beanFactory, considerHierarchy, resolve(type, classLoader),
262281
parameterizedContainers);
263282
}
264283
catch (ClassNotFoundException | NoClassDefFoundError ex) {
265-
return Collections.emptySet();
284+
return Collections.emptyMap();
266285
}
267286
}
268287

269-
private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type,
270-
Set<Class<?>> parameterizedContainers) {
271-
Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers,
272-
null);
273-
return (result != null) ? result : Collections.emptySet();
288+
private Map<String, BeanDefinition> getBeanDefinitionsForType(ListableBeanFactory beanFactory,
289+
boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers) {
290+
Map<String, BeanDefinition> result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type,
291+
parameterizedContainers, null);
292+
return (result != null) ? result : Collections.emptyMap();
274293
}
275294

276-
private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
277-
Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) {
278-
result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));
295+
private Map<String, BeanDefinition> collectBeanDefinitionsForType(ListableBeanFactory beanFactory,
296+
boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers,
297+
Map<String, BeanDefinition> result) {
298+
result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory);
279299
for (Class<?> container : parameterizedContainers) {
280300
ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);
281-
result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));
301+
result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory);
282302
}
283303
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) {
284304
BeanFactory parent = hierarchicalBeanFactory.getParentBeanFactory();
285305
if (parent instanceof ListableBeanFactory listableBeanFactory) {
286-
result = collectBeanNamesForType(listableBeanFactory, considerHierarchy, type, parameterizedContainers,
287-
result);
306+
result = collectBeanDefinitionsForType(listableBeanFactory, considerHierarchy, type,
307+
parameterizedContainers, result);
288308
}
289309
}
290310
return result;
291311
}
292312

293-
private Set<String> getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory,
294-
String type, boolean considerHierarchy) throws LinkageError {
295-
Set<String> result = null;
313+
private Map<String, BeanDefinition> getBeanDefinitionsForAnnotation(ClassLoader classLoader,
314+
ConfigurableListableBeanFactory beanFactory, String type, boolean considerHierarchy) throws LinkageError {
315+
Map<String, BeanDefinition> result = null;
296316
try {
297-
result = collectBeanNamesForAnnotation(beanFactory, resolveAnnotationType(classLoader, type),
317+
result = collectBeanDefinitionsForAnnotation(beanFactory, resolveAnnotationType(classLoader, type),
298318
considerHierarchy, result);
299319
}
300320
catch (ClassNotFoundException ex) {
301321
// Continue
302322
}
303-
return (result != null) ? result : Collections.emptySet();
323+
return (result != null) ? result : Collections.emptyMap();
304324
}
305325

306326
@SuppressWarnings("unchecked")
@@ -309,13 +329,14 @@ private Class<? extends Annotation> resolveAnnotationType(ClassLoader classLoade
309329
return (Class<? extends Annotation>) resolve(type, classLoader);
310330
}
311331

312-
private Set<String> collectBeanNamesForAnnotation(ListableBeanFactory beanFactory,
313-
Class<? extends Annotation> annotationType, boolean considerHierarchy, Set<String> result) {
314-
result = addAll(result, getBeanNamesForAnnotation(beanFactory, annotationType));
332+
private Map<String, BeanDefinition> collectBeanDefinitionsForAnnotation(ListableBeanFactory beanFactory,
333+
Class<? extends Annotation> annotationType, boolean considerHierarchy, Map<String, BeanDefinition> result) {
334+
result = putAll(result, getBeanNamesForAnnotation(beanFactory, annotationType), beanFactory);
315335
if (considerHierarchy) {
316336
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
317337
if (parent instanceof ListableBeanFactory listableBeanFactory) {
318-
result = collectBeanNamesForAnnotation(listableBeanFactory, annotationType, considerHierarchy, result);
338+
result = collectBeanDefinitionsForAnnotation(listableBeanFactory, annotationType, considerHierarchy,
339+
result);
319340
}
320341
}
321342
return result;
@@ -453,12 +474,27 @@ private static Set<String> addAll(Set<String> result, Collection<String> additio
453474
return result;
454475
}
455476

456-
private static Set<String> addAll(Set<String> result, String[] additional) {
457-
if (ObjectUtils.isEmpty(additional)) {
477+
private static Map<String, BeanDefinition> putAll(Map<String, BeanDefinition> result, String[] beanNames,
478+
ListableBeanFactory beanFactory) {
479+
if (ObjectUtils.isEmpty(beanNames)) {
458480
return result;
459481
}
460-
result = (result != null) ? result : new LinkedHashSet<>();
461-
Collections.addAll(result, additional);
482+
if (result == null) {
483+
result = new LinkedHashMap<>();
484+
}
485+
for (String beanName : beanNames) {
486+
if (beanFactory instanceof ConfigurableListableBeanFactory clbf) {
487+
try {
488+
result.put(beanName, clbf.getBeanDefinition(beanName));
489+
}
490+
catch (NoSuchBeanDefinitionException ex) {
491+
result.put(beanName, null);
492+
}
493+
}
494+
else {
495+
result.put(beanName, null);
496+
}
497+
}
462498
return result;
463499
}
464500

0 commit comments

Comments
 (0)