Skip to content

Commit a8fb16b

Browse files
committed
Introduce defaultCandidate flag (for plain type vs. qualified match)
Closes gh-26528
1 parent bc2257a commit a8fb16b

File tree

5 files changed

+72
-14
lines changed

5 files changed

+72
-14
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] an
167167
if (ObjectUtils.isEmpty(annotationsToSearch)) {
168168
return true;
169169
}
170+
boolean qualifierFound = false;
170171
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
171172
for (Annotation annotation : annotationsToSearch) {
172173
Class<? extends Annotation> type = annotation.annotationType();
173174
boolean checkMeta = true;
174175
boolean fallbackToMeta = false;
175176
if (isQualifier(type)) {
177+
qualifierFound = true;
176178
if (!checkQualifier(bdHolder, annotation, typeConverter)) {
177179
fallbackToMeta = true;
178180
}
@@ -185,6 +187,7 @@ protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] an
185187
for (Annotation metaAnn : type.getAnnotations()) {
186188
Class<? extends Annotation> metaType = metaAnn.annotationType();
187189
if (isQualifier(metaType)) {
190+
qualifierFound = true;
188191
foundMeta = true;
189192
// Only accept fallback match if @Qualifier annotation has a value...
190193
// Otherwise, it is just a marker for a custom qualifier annotation.
@@ -199,7 +202,7 @@ protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] an
199202
}
200203
}
201204
}
202-
return true;
205+
return (qualifierFound || ((RootBeanDefinition) bdHolder.getBeanDefinition()).isDefaultCandidate());
203206
}
204207

205208
/**

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java

+36-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-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.
@@ -185,6 +185,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
185185

186186
private boolean autowireCandidate = true;
187187

188+
private boolean defaultCandidate = true;
189+
188190
private boolean primary = false;
189191

190192
private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
@@ -284,6 +286,7 @@ protected AbstractBeanDefinition(BeanDefinition original) {
284286
setDependencyCheck(originalAbd.getDependencyCheck());
285287
setDependsOn(originalAbd.getDependsOn());
286288
setAutowireCandidate(originalAbd.isAutowireCandidate());
289+
setDefaultCandidate(originalAbd.isDefaultCandidate());
287290
setPrimary(originalAbd.isPrimary());
288291
copyQualifiersFrom(originalAbd);
289292
setInstanceSupplier(originalAbd.getInstanceSupplier());
@@ -360,6 +363,7 @@ public void overrideFrom(BeanDefinition other) {
360363
setDependencyCheck(otherAbd.getDependencyCheck());
361364
setDependsOn(otherAbd.getDependsOn());
362365
setAutowireCandidate(otherAbd.isAutowireCandidate());
366+
setDefaultCandidate(otherAbd.isDefaultCandidate());
363367
setPrimary(otherAbd.isPrimary());
364368
copyQualifiersFrom(otherAbd);
365369
setInstanceSupplier(otherAbd.getInstanceSupplier());
@@ -686,7 +690,10 @@ public String[] getDependsOn() {
686690
}
687691

688692
/**
689-
* Set whether this bean is a candidate for getting autowired into some other bean.
693+
* Set whether this bean is a candidate for getting autowired into some other
694+
* bean at all.
695+
* <p>Default is {@code true}, allowing injection by type at any injection point.
696+
* Switch this to {@code false} in order to disable autowiring by type for this bean.
690697
* <p>Note that this flag is designed to only affect type-based autowiring.
691698
* It does not affect explicit references by name, which will get resolved even
692699
* if the specified bean is not marked as an autowire candidate. As a consequence,
@@ -700,17 +707,41 @@ public void setAutowireCandidate(boolean autowireCandidate) {
700707
}
701708

702709
/**
703-
* Return whether this bean is a candidate for getting autowired into some other bean.
710+
* Return whether this bean is a candidate for getting autowired into some other
711+
* bean at all.
704712
*/
705713
@Override
706714
public boolean isAutowireCandidate() {
707715
return this.autowireCandidate;
708716
}
709717

718+
/**
719+
* Set whether this bean is a candidate for getting autowired into some other
720+
* bean based on the plain type, without any further indications such as a
721+
* qualifier match.
722+
* <p>Default is {@code true}, allowing injection by type at any injection point.
723+
* Switch this to {@code false} in order to restrict injection by default,
724+
* effectively enforcing an additional indication such as a qualifier match.
725+
* @since 6.2
726+
*/
727+
public void setDefaultCandidate(boolean defaultCandidate) {
728+
this.defaultCandidate = defaultCandidate;
729+
}
730+
731+
/**
732+
* Return whether this bean is a candidate for getting autowired into some other
733+
* bean based on the plain type, without any further indications such as a
734+
* qualifier match?
735+
* @since 6.2
736+
*/
737+
public boolean isDefaultCandidate() {
738+
return this.defaultCandidate;
739+
}
740+
710741
/**
711742
* Set whether this bean is a primary autowire candidate.
712-
* <p>If this value is {@code true} for exactly one bean among multiple
713-
* matching candidates, it will serve as a tie-breaker.
743+
* <p>Default is {@code false}. If this value is {@code true} for exactly one
744+
* bean among multiple matching candidates, it will serve as a tie-breaker.
714745
*/
715746
@Override
716747
public void setPrimary(boolean primary) {

Diff for: spring-context/src/main/java/org/springframework/context/annotation/Bean.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-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.
@@ -239,13 +239,27 @@
239239
String[] name() default {};
240240

241241
/**
242-
* Is this bean a candidate for getting autowired into some other bean?
242+
* Is this bean a candidate for getting autowired into some other bean at all?
243243
* <p>Default is {@code true}; set this to {@code false} for internal delegates
244244
* that are not meant to get in the way of beans of the same type in other places.
245245
* @since 5.1
246+
* @see #defaultCandidate()
246247
*/
247248
boolean autowireCandidate() default true;
248249

250+
/**
251+
* Is this bean a candidate for getting autowired into some other bean based on
252+
* the plain type, without any further indications such as a qualifier match?
253+
* <p>Default is {@code true}; set this to {@code false} for restricted delegates
254+
* that are supposed to be injectable in certain areas but are not meant to get
255+
* in the way of beans of the same type in other places.
256+
* <p>This is a variation of {@link #autowireCandidate()} which does not disable
257+
* injection in general, just enforces an additional indication such as a qualifier.
258+
* @since 6.2
259+
* @see #autowireCandidate()
260+
*/
261+
boolean defaultCandidate() default true;
262+
249263
/**
250264
* The optional name of a method to call on the bean instance during initialization.
251265
* Not commonly used, given that the method may be called programmatically directly

Diff for: spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-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.
@@ -241,6 +241,11 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
241241
beanDef.setAutowireCandidate(false);
242242
}
243243

244+
boolean defaultCandidate = bean.getBoolean("defaultCandidate");
245+
if (!defaultCandidate) {
246+
beanDef.setDefaultCandidate(false);
247+
}
248+
244249
String initMethodName = bean.getString("initMethod");
245250
if (StringUtils.hasText(initMethodName)) {
246251
beanDef.setInitMethodName(initMethodName);

Diff for: spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-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.
@@ -89,6 +89,7 @@ void customWithLazyResolution() {
8989
assertThat(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"),
9090
"testBean2", ctx.getDefaultListableBeanFactory())).isTrue();
9191
CustomPojo pojo = ctx.getBean(CustomPojo.class);
92+
assertThat(pojo.plainBean).isNull();
9293
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
9394
TestBean testBean2 = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
9495
ctx.getDefaultListableBeanFactory(), TestBean.class, "boring");
@@ -132,7 +133,9 @@ void customWithAttributeOverride() {
132133
new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class);
133134
assertThat(ctx.getBeanFactory().containsSingleton("testBeanX")).isFalse();
134135
CustomPojo pojo = ctx.getBean(CustomPojo.class);
136+
assertThat(pojo.plainBean).isNull();
135137
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
138+
assertThat(pojo.nestedTestBean).isNull();
136139
ctx.close();
137140
}
138141

@@ -219,7 +222,7 @@ public TestBean testBean1() {
219222
return new TestBean("interesting");
220223
}
221224

222-
@Bean @Qualifier("boring") @Lazy
225+
@Bean(defaultCandidate=false) @Qualifier("boring") @Lazy
223226
public TestBean testBean2(@Lazy TestBean testBean1) {
224227
TestBean tb = new TestBean("boring");
225228
tb.setSpouse(testBean1);
@@ -235,7 +238,7 @@ public TestBean testBean1() {
235238
return new TestBean("interesting");
236239
}
237240

238-
@Bean @Qualifier("boring")
241+
@Bean(defaultCandidate=false) @Qualifier("boring")
239242
public TestBean testBean2(@Lazy TestBean testBean1) {
240243
TestBean tb = new TestBean("boring");
241244
tb.setSpouse(testBean1);
@@ -246,17 +249,19 @@ public TestBean testBean2(@Lazy TestBean testBean1) {
246249
@InterestingPojo
247250
static class CustomPojo {
248251

252+
@Autowired(required=false) TestBean plainBean;
253+
249254
@InterestingNeed TestBean testBean;
250255

251256
@InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean;
252257
}
253258

254-
@Bean @Lazy @Qualifier("interesting")
259+
@Bean(defaultCandidate=false) @Lazy @Qualifier("interesting")
255260
@Retention(RetentionPolicy.RUNTIME)
256261
@interface InterestingBean {
257262
}
258263

259-
@Bean @Lazy @Qualifier("interesting")
264+
@Bean(defaultCandidate=false) @Lazy @Qualifier("interesting")
260265
@Retention(RetentionPolicy.RUNTIME)
261266
@interface InterestingBeanWithName {
262267

0 commit comments

Comments
 (0)