diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Fallback.java b/spring-context/src/main/java/org/springframework/context/annotation/Fallback.java
index 9ff6d16d73..32544002ce 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/Fallback.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/Fallback.java
@@ -29,6 +29,11 @@ import java.lang.annotation.Target;
*
If all beans but one among multiple matching candidates are marked
* as a fallback, the remaining bean will be selected.
*
+ *
Just like primary beans, fallback beans only have an effect when
+ * finding multiple candidates for single injection points.
+ * All type-matching beans are included when autowiring arrays,
+ * collections, maps, or ObjectProvider streams.
+ *
* @author Juergen Hoeller
* @since 6.2
* @see Primary
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Primary.java b/spring-context/src/main/java/org/springframework/context/annotation/Primary.java
index 5ff345fa7a..92c26789f4 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/Primary.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/Primary.java
@@ -27,6 +27,10 @@ import java.lang.annotation.Target;
* are qualified to autowire a single-valued dependency. If exactly one
* 'primary' bean exists among the candidates, it will be the autowired value.
*
+ *
Primary beans only have an effect when finding multiple candidates
+ * for single injection points. All type-matching beans are included when
+ * autowiring arrays, collections, maps, or ObjectProvider streams.
+ *
*
This annotation is semantically equivalent to the {@code } element's
* {@code primary} attribute in Spring XML.
*
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java
index d9ed7e70d0..cb42e8fcb6 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java
@@ -18,9 +18,13 @@ package org.springframework.context.annotation.configuration;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
@@ -38,6 +42,7 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Chris Beams
* @author Juergen Hoeller
+ * @author Yanming Zhou
*/
class BeanMethodQualificationTests {
@@ -54,10 +60,12 @@ class BeanMethodQualificationTests {
void standard() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(StandardConfig.class, StandardPojo.class);
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isFalse();
StandardPojo pojo = ctx.getBean(StandardPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+
ctx.close();
}
@@ -65,10 +73,12 @@ class BeanMethodQualificationTests {
void scoped() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(ScopedConfig.class, StandardPojo.class);
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isFalse();
StandardPojo pojo = ctx.getBean(StandardPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+
ctx.close();
}
@@ -76,36 +86,37 @@ class BeanMethodQualificationTests {
void scopedProxy() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(ScopedProxyConfig.class, StandardPojo.class);
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isTrue(); // a shared scoped proxy
StandardPojo pojo = ctx.getBean(StandardPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+
ctx.close();
}
- @Test
- void primary() {
+ @SuppressWarnings("unchecked")
+ @ParameterizedTest
+ @ValueSource(classes = {PrimaryConfig.class, FallbackConfig.class})
+ void primaryVersusFallback(Class> configClass) {
AnnotationConfigApplicationContext ctx =
- new AnnotationConfigApplicationContext(PrimaryConfig.class, StandardPojo.class, ConstructorPojo.class);
- StandardPojo pojo = ctx.getBean(StandardPojo.class);
- assertThat(pojo.testBean.getName()).isEqualTo("interesting");
- assertThat(pojo.testBean2.getName()).isEqualTo("boring");
- ConstructorPojo pojo2 = ctx.getBean(ConstructorPojo.class);
- assertThat(pojo2.testBean.getName()).isEqualTo("interesting");
- assertThat(pojo2.testBean2.getName()).isEqualTo("boring");
- ctx.close();
- }
+ new AnnotationConfigApplicationContext(configClass, StandardPojo.class, ConstructorPojo.class);
- @Test
- void fallback() {
- AnnotationConfigApplicationContext ctx =
- new AnnotationConfigApplicationContext(FallbackConfig.class, StandardPojo.class, ConstructorPojo.class);
StandardPojo pojo = ctx.getBean(StandardPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+ assertThat(pojo.testBean2.getSpouse().getName()).isEqualTo("interesting");
+ assertThat((List) pojo.testBean2.getPets()).contains(
+ ctx.getBean("testBean1x"), ctx.getBean("testBean2x")); // array injection
+ assertThat((List) pojo.testBean2.getSomeList()).contains(
+ ctx.getBean("testBean1x"), ctx.getBean("testBean2x")); // list injection
+ assertThat((Map) pojo.testBean2.getSomeMap()).containsKeys(
+ "testBean1x", "testBean2x"); // map injection
+
ConstructorPojo pojo2 = ctx.getBean(ConstructorPojo.class);
- assertThat(pojo2.testBean.getName()).isEqualTo("interesting");
- assertThat(pojo2.testBean2.getName()).isEqualTo("boring");
+ assertThat(pojo2.testBean).isSameAs(pojo.testBean);
+ assertThat(pojo2.testBean2).isSameAs(pojo.testBean2);
+
ctx.close();
}
@@ -113,16 +124,20 @@ class BeanMethodQualificationTests {
void customWithLazyResolution() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(CustomConfig.class, CustomPojo.class);
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isFalse();
assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isFalse();
assertThat(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"),
"testBean2", ctx.getDefaultListableBeanFactory())).isTrue();
+
CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
+
TestBean testBean2 = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
ctx.getDefaultListableBeanFactory(), TestBean.class, "boring");
assertThat(testBean2.getName()).isEqualTo("boring");
+
ctx.close();
}
@@ -131,13 +146,16 @@ class BeanMethodQualificationTests {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(CustomConfig.class, CustomPojo.class);
ctx.refresh();
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isFalse();
assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isFalse();
ctx.getBean("testBean2");
assertThat(BeanFactoryAnnotationUtils.isQualifierMatch(value -> value.equals("boring"),
"testBean2", ctx.getDefaultListableBeanFactory())).isTrue();
+
CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
+
ctx.close();
}
@@ -149,10 +167,12 @@ class BeanMethodQualificationTests {
customPojo.setLazyInit(true);
ctx.registerBeanDefinition("customPojo", customPojo);
ctx.refresh();
+
assertThat(ctx.getBeanFactory().containsSingleton("testBean1")).isFalse();
assertThat(ctx.getBeanFactory().containsSingleton("testBean2")).isFalse();
CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
+
ctx.close();
}
@@ -160,21 +180,29 @@ class BeanMethodQualificationTests {
void customWithAttributeOverride() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class);
+
assertThat(ctx.getBeanFactory().containsSingleton("testBeanX")).isFalse();
CustomPojo pojo = ctx.getBean(CustomPojo.class);
assertThat(pojo.plainBean).isNull();
assertThat(pojo.testBean.getName()).isEqualTo("interesting");
assertThat(pojo.nestedTestBean).isNull();
+
ctx.close();
}
@Test
void beanNamesForAnnotation() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(StandardConfig.class);
- assertThat(ctx.getBeanNamesForAnnotation(Configuration.class)).isEqualTo(new String[] {"beanMethodQualificationTests.StandardConfig"});
- assertThat(ctx.getBeanNamesForAnnotation(Scope.class)).isEqualTo(new String[] {});
- assertThat(ctx.getBeanNamesForAnnotation(Lazy.class)).isEqualTo(new String[] {"testBean1"});
- assertThat(ctx.getBeanNamesForAnnotation(Boring.class)).isEqualTo(new String[] {"beanMethodQualificationTests.StandardConfig", "testBean2"});
+
+ assertThat(ctx.getBeanNamesForAnnotation(Configuration.class)).isEqualTo(
+ new String[] {"beanMethodQualificationTests.StandardConfig"});
+ assertThat(ctx.getBeanNamesForAnnotation(Scope.class)).isEqualTo(
+ new String[] {});
+ assertThat(ctx.getBeanNamesForAnnotation(Lazy.class)).isEqualTo(
+ new String[] {"testBean1"});
+ assertThat(ctx.getBeanNamesForAnnotation(Boring.class)).isEqualTo(
+ new String[] {"beanMethodQualificationTests.StandardConfig", "testBean2"});
+
ctx.close();
}
@@ -196,6 +224,7 @@ class BeanMethodQualificationTests {
}
}
+
@Configuration
@Boring
static class ScopedConfig {
@@ -213,6 +242,7 @@ class BeanMethodQualificationTests {
}
}
+
@Configuration
@Boring
static class ScopedProxyConfig {
@@ -230,6 +260,7 @@ class BeanMethodQualificationTests {
}
}
+
@Configuration
static class PrimaryConfig {
@@ -244,9 +275,14 @@ class BeanMethodQualificationTests {
}
@Bean @Boring @Primary
- public TestBean testBean2(TestBean testBean1) {
+ public TestBean testBean2(TestBean testBean1, TestBean[] testBeanArray,
+ List testBeanList, Map testBeanMap) {
+
TestBean tb = new TestBean("boring");
tb.setSpouse(testBean1);
+ tb.setPets(CollectionUtils.arrayToList(testBeanArray));
+ tb.setSomeList(testBeanList);
+ tb.setSomeMap(testBeanMap);
return tb;
}
@@ -256,6 +292,7 @@ class BeanMethodQualificationTests {
}
}
+
@Configuration
static class FallbackConfig {
@@ -270,9 +307,14 @@ class BeanMethodQualificationTests {
}
@Bean @Boring
- public TestBean testBean2(TestBean testBean1) {
+ public TestBean testBean2(TestBean testBean1, TestBean[] testBeanArray,
+ List testBeanList, Map testBeanMap) {
+
TestBean tb = new TestBean("boring");
tb.setSpouse(testBean1);
+ tb.setPets(CollectionUtils.arrayToList(testBeanArray));
+ tb.setSomeList(testBeanList);
+ tb.setSomeMap(testBeanMap);
return tb;
}
@@ -282,6 +324,7 @@ class BeanMethodQualificationTests {
}
}
+
@Component @Lazy
static class StandardPojo {
@@ -290,6 +333,7 @@ class BeanMethodQualificationTests {
@Autowired @Boring TestBean testBean2;
}
+
@Component @Lazy
static class ConstructorPojo {
@@ -303,10 +347,6 @@ class BeanMethodQualificationTests {
}
}
- @Qualifier
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Boring {
- }
@Configuration
static class CustomConfig {
@@ -324,6 +364,7 @@ class BeanMethodQualificationTests {
}
}
+
@Configuration
static class CustomConfigWithAttributeOverride {
@@ -340,6 +381,7 @@ class BeanMethodQualificationTests {
}
}
+
@InterestingPojo
static class CustomPojo {
@@ -354,6 +396,12 @@ class BeanMethodQualificationTests {
}
}
+
+ @Qualifier
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Boring {
+ }
+
@Bean(defaultCandidate=false) @Lazy @Qualifier("interesting")
@Retention(RetentionPolicy.RUNTIME)
@interface InterestingBean {