Introduce defaultCandidate flag (for plain type vs. qualified match)

Closes gh-26528
This commit is contained in:
Juergen Hoeller 2024-02-20 12:00:47 +01:00
parent bc2257aaff
commit a8fb16b47c
5 changed files with 72 additions and 14 deletions

View File

@ -167,12 +167,14 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
if (ObjectUtils.isEmpty(annotationsToSearch)) { if (ObjectUtils.isEmpty(annotationsToSearch)) {
return true; return true;
} }
boolean qualifierFound = false;
SimpleTypeConverter typeConverter = new SimpleTypeConverter(); SimpleTypeConverter typeConverter = new SimpleTypeConverter();
for (Annotation annotation : annotationsToSearch) { for (Annotation annotation : annotationsToSearch) {
Class<? extends Annotation> type = annotation.annotationType(); Class<? extends Annotation> type = annotation.annotationType();
boolean checkMeta = true; boolean checkMeta = true;
boolean fallbackToMeta = false; boolean fallbackToMeta = false;
if (isQualifier(type)) { if (isQualifier(type)) {
qualifierFound = true;
if (!checkQualifier(bdHolder, annotation, typeConverter)) { if (!checkQualifier(bdHolder, annotation, typeConverter)) {
fallbackToMeta = true; fallbackToMeta = true;
} }
@ -185,6 +187,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
for (Annotation metaAnn : type.getAnnotations()) { for (Annotation metaAnn : type.getAnnotations()) {
Class<? extends Annotation> metaType = metaAnn.annotationType(); Class<? extends Annotation> metaType = metaAnn.annotationType();
if (isQualifier(metaType)) { if (isQualifier(metaType)) {
qualifierFound = true;
foundMeta = true; foundMeta = true;
// Only accept fallback match if @Qualifier annotation has a value... // Only accept fallback match if @Qualifier annotation has a value...
// Otherwise, it is just a marker for a custom qualifier annotation. // Otherwise, it is just a marker for a custom qualifier annotation.
@ -199,7 +202,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
} }
} }
} }
return true; return (qualifierFound || ((RootBeanDefinition) bdHolder.getBeanDefinition()).isDefaultCandidate());
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -185,6 +185,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
private boolean autowireCandidate = true; private boolean autowireCandidate = true;
private boolean defaultCandidate = true;
private boolean primary = false; private boolean primary = false;
private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>(); private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();
@ -284,6 +286,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setDependencyCheck(originalAbd.getDependencyCheck()); setDependencyCheck(originalAbd.getDependencyCheck());
setDependsOn(originalAbd.getDependsOn()); setDependsOn(originalAbd.getDependsOn());
setAutowireCandidate(originalAbd.isAutowireCandidate()); setAutowireCandidate(originalAbd.isAutowireCandidate());
setDefaultCandidate(originalAbd.isDefaultCandidate());
setPrimary(originalAbd.isPrimary()); setPrimary(originalAbd.isPrimary());
copyQualifiersFrom(originalAbd); copyQualifiersFrom(originalAbd);
setInstanceSupplier(originalAbd.getInstanceSupplier()); setInstanceSupplier(originalAbd.getInstanceSupplier());
@ -360,6 +363,7 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
setDependencyCheck(otherAbd.getDependencyCheck()); setDependencyCheck(otherAbd.getDependencyCheck());
setDependsOn(otherAbd.getDependsOn()); setDependsOn(otherAbd.getDependsOn());
setAutowireCandidate(otherAbd.isAutowireCandidate()); setAutowireCandidate(otherAbd.isAutowireCandidate());
setDefaultCandidate(otherAbd.isDefaultCandidate());
setPrimary(otherAbd.isPrimary()); setPrimary(otherAbd.isPrimary());
copyQualifiersFrom(otherAbd); copyQualifiersFrom(otherAbd);
setInstanceSupplier(otherAbd.getInstanceSupplier()); setInstanceSupplier(otherAbd.getInstanceSupplier());
@ -686,7 +690,10 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
} }
/** /**
* Set whether this bean is a candidate for getting autowired into some other bean. * Set whether this bean is a candidate for getting autowired into some other
* bean at all.
* <p>Default is {@code true}, allowing injection by type at any injection point.
* Switch this to {@code false} in order to disable autowiring by type for this bean.
* <p>Note that this flag is designed to only affect type-based autowiring. * <p>Note that this flag is designed to only affect type-based autowiring.
* It does not affect explicit references by name, which will get resolved even * It does not affect explicit references by name, which will get resolved even
* if the specified bean is not marked as an autowire candidate. As a consequence, * if the specified bean is not marked as an autowire candidate. As a consequence,
@ -700,17 +707,41 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
} }
/** /**
* Return whether this bean is a candidate for getting autowired into some other bean. * Return whether this bean is a candidate for getting autowired into some other
* bean at all.
*/ */
@Override @Override
public boolean isAutowireCandidate() { public boolean isAutowireCandidate() {
return this.autowireCandidate; return this.autowireCandidate;
} }
/**
* Set whether this bean is a candidate for getting autowired into some other
* bean based on the plain type, without any further indications such as a
* qualifier match.
* <p>Default is {@code true}, allowing injection by type at any injection point.
* Switch this to {@code false} in order to restrict injection by default,
* effectively enforcing an additional indication such as a qualifier match.
* @since 6.2
*/
public void setDefaultCandidate(boolean defaultCandidate) {
this.defaultCandidate = defaultCandidate;
}
/**
* Return whether this bean is a candidate for getting autowired into some other
* bean based on the plain type, without any further indications such as a
* qualifier match?
* @since 6.2
*/
public boolean isDefaultCandidate() {
return this.defaultCandidate;
}
/** /**
* Set whether this bean is a primary autowire candidate. * Set whether this bean is a primary autowire candidate.
* <p>If this value is {@code true} for exactly one bean among multiple * <p>Default is {@code false}. If this value is {@code true} for exactly one
* matching candidates, it will serve as a tie-breaker. * bean among multiple matching candidates, it will serve as a tie-breaker.
*/ */
@Override @Override
public void setPrimary(boolean primary) { public void setPrimary(boolean primary) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -239,13 +239,27 @@ public @interface Bean {
String[] name() default {}; String[] name() default {};
/** /**
* Is this bean a candidate for getting autowired into some other bean? * Is this bean a candidate for getting autowired into some other bean at all?
* <p>Default is {@code true}; set this to {@code false} for internal delegates * <p>Default is {@code true}; set this to {@code false} for internal delegates
* that are not meant to get in the way of beans of the same type in other places. * that are not meant to get in the way of beans of the same type in other places.
* @since 5.1 * @since 5.1
* @see #defaultCandidate()
*/ */
boolean autowireCandidate() default true; boolean autowireCandidate() default true;
/**
* Is this bean a candidate for getting autowired into some other bean based on
* the plain type, without any further indications such as a qualifier match?
* <p>Default is {@code true}; set this to {@code false} for restricted delegates
* that are supposed to be injectable in certain areas but are not meant to get
* in the way of beans of the same type in other places.
* <p>This is a variation of {@link #autowireCandidate()} which does not disable
* injection in general, just enforces an additional indication such as a qualifier.
* @since 6.2
* @see #autowireCandidate()
*/
boolean defaultCandidate() default true;
/** /**
* The optional name of a method to call on the bean instance during initialization. * The optional name of a method to call on the bean instance during initialization.
* Not commonly used, given that the method may be called programmatically directly * Not commonly used, given that the method may be called programmatically directly

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -241,6 +241,11 @@ class ConfigurationClassBeanDefinitionReader {
beanDef.setAutowireCandidate(false); beanDef.setAutowireCandidate(false);
} }
boolean defaultCandidate = bean.getBoolean("defaultCandidate");
if (!defaultCandidate) {
beanDef.setDefaultCandidate(false);
}
String initMethodName = bean.getString("initMethod"); String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) { if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName); beanDef.setInitMethodName(initMethodName);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -89,6 +89,7 @@ class BeanMethodQualificationTests {
assertThat(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"), assertThat(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"),
"testBean2", ctx.getDefaultListableBeanFactory())).isTrue(); "testBean2", ctx.getDefaultListableBeanFactory())).isTrue();
CustomPojo pojo = ctx.getBean(CustomPojo.class); CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting"); assertThat(pojo.testBean.getName()).isEqualTo("interesting");
TestBean testBean2 = BeanFactoryAnnotationUtils.qualifiedBeanOfType( TestBean testBean2 = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
ctx.getDefaultListableBeanFactory(), TestBean.class, "boring"); ctx.getDefaultListableBeanFactory(), TestBean.class, "boring");
@ -132,7 +133,9 @@ class BeanMethodQualificationTests {
new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class); new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class);
assertThat(ctx.getBeanFactory().containsSingleton("testBeanX")).isFalse(); assertThat(ctx.getBeanFactory().containsSingleton("testBeanX")).isFalse();
CustomPojo pojo = ctx.getBean(CustomPojo.class); CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting"); assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.nestedTestBean).isNull();
ctx.close(); ctx.close();
} }
@ -219,7 +222,7 @@ class BeanMethodQualificationTests {
return new TestBean("interesting"); return new TestBean("interesting");
} }
@Bean @Qualifier("boring") @Lazy @Bean(defaultCandidate=false) @Qualifier("boring") @Lazy
public TestBean testBean2(@Lazy TestBean testBean1) { public TestBean testBean2(@Lazy TestBean testBean1) {
TestBean tb = new TestBean("boring"); TestBean tb = new TestBean("boring");
tb.setSpouse(testBean1); tb.setSpouse(testBean1);
@ -235,7 +238,7 @@ class BeanMethodQualificationTests {
return new TestBean("interesting"); return new TestBean("interesting");
} }
@Bean @Qualifier("boring") @Bean(defaultCandidate=false) @Qualifier("boring")
public TestBean testBean2(@Lazy TestBean testBean1) { public TestBean testBean2(@Lazy TestBean testBean1) {
TestBean tb = new TestBean("boring"); TestBean tb = new TestBean("boring");
tb.setSpouse(testBean1); tb.setSpouse(testBean1);
@ -246,17 +249,19 @@ class BeanMethodQualificationTests {
@InterestingPojo @InterestingPojo
static class CustomPojo { static class CustomPojo {
@Autowired(required=false) TestBean plainBean;
@InterestingNeed TestBean testBean; @InterestingNeed TestBean testBean;
@InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean; @InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean;
} }
@Bean @Lazy @Qualifier("interesting") @Bean(defaultCandidate=false) @Lazy @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface InterestingBean { @interface InterestingBean {
} }
@Bean @Lazy @Qualifier("interesting") @Bean(defaultCandidate=false) @Lazy @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface InterestingBeanWithName { @interface InterestingBeanWithName {