diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java index 5be39a0eaa1..aee39bc138e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/BeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -178,6 +178,7 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { * Set whether this bean is a primary autowire candidate. *
If this value is {@code true} for exactly one bean among multiple * matching candidates, it will serve as a tie-breaker. + * @see #setFallback */ void setPrimary(boolean primary); @@ -186,6 +187,21 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { */ boolean isPrimary(); + /** + * Set whether this bean is a fallback autowire candidate. + *
If this value is {@code true} for all beans but one among multiple
+ * matching candidates, the remaining bean will be selected.
+ * @since 6.2
+ * @see #setPrimary
+ */
+ void setFallback(boolean fallback);
+
+ /**
+ * Return whether this bean is a fallback autowire candidate.
+ * @since 6.2
+ */
+ boolean isFallback();
+
/**
* Specify the factory bean to use, if any.
* This the name of the bean to call the specified factory method on.
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java
index 6222cca3c69..85c7e5cadab 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java
@@ -189,6 +189,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
private boolean primary = false;
+ private boolean fallback = false;
+
private final Map Default is {@code false}. If this value is {@code true} for exactly one
* bean among multiple matching candidates, it will serve as a tie-breaker.
+ * @see #setFallback
*/
@Override
public void setPrimary(boolean primary) {
@@ -756,6 +761,25 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
return this.primary;
}
+ /**
+ * Set whether this bean is a fallback autowire candidate.
+ * Default is {@code false}. If this value is {@code true} for all beans but
+ * one among multiple matching candidates, the remaining bean will be selected.
+ * @since 6.2
+ * @see #setPrimary
+ */
+ public void setFallback(boolean fallback) {
+ this.fallback = fallback;
+ }
+
+ /**
+ * Return whether this bean is a fallback autowire candidate.
+ * @since 6.2
+ */
+ public boolean isFallback() {
+ return this.fallback;
+ }
+
/**
* Register a qualifier to be used for autowire candidate resolution,
* keyed by the qualifier's type name.
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
index 0bcd895f7f6..67c9142e5d7 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
@@ -1796,6 +1796,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Nullable
protected String determinePrimaryCandidate(Map If all beans but one among multiple matching candidates are marked
+ * as a fallback, the remaining bean will be selected.
+ *
+ * @author Juergen Hoeller
+ * @since 6.2
+ * @see Primary
+ * @see Lazy
+ * @see Bean
+ * @see org.springframework.beans.factory.config.BeanDefinition#setFallback
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Fallback {
+
+}
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 3832996e448..5ff345fa7a6 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,10 +77,12 @@ import java.lang.annotation.Target;
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
+ * @see Fallback
* @see Lazy
* @see Bean
* @see ComponentScan
* @see org.springframework.stereotype.Component
+ * @see org.springframework.beans.factory.config.BeanDefinition#setPrimary
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
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 a99227b6476..cd969c928d5 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
@@ -30,7 +30,9 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Fallback;
import org.springframework.context.annotation.Lazy;
+import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.annotation.AliasFor;
@@ -80,6 +82,26 @@ class BeanMethodQualificationTests {
ctx.close();
}
+ @Test
+ void primary() {
+ AnnotationConfigApplicationContext ctx =
+ new AnnotationConfigApplicationContext(PrimaryConfig.class, StandardPojo.class);
+ StandardPojo pojo = ctx.getBean(StandardPojo.class);
+ assertThat(pojo.testBean.getName()).isEqualTo("interesting");
+ assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+ ctx.close();
+ }
+
+ @Test
+ void fallback() {
+ AnnotationConfigApplicationContext ctx =
+ new AnnotationConfigApplicationContext(FallbackConfig.class, StandardPojo.class);
+ StandardPojo pojo = ctx.getBean(StandardPojo.class);
+ assertThat(pojo.testBean.getName()).isEqualTo("interesting");
+ assertThat(pojo.testBean2.getName()).isEqualTo("boring");
+ ctx.close();
+ }
+
@Test
void customWithLazyResolution() {
AnnotationConfigApplicationContext ctx =
@@ -201,6 +223,58 @@ class BeanMethodQualificationTests {
}
}
+ @Configuration
+ static class PrimaryConfig {
+
+ @Bean @Qualifier("interesting") @Primary
+ public static TestBean testBean1() {
+ return new TestBean("interesting");
+ }
+
+ @Bean @Qualifier("interesting")
+ public static TestBean testBean1x() {
+ return new TestBean("interesting");
+ }
+
+ @Bean @Boring @Primary
+ public TestBean testBean2(TestBean testBean1) {
+ TestBean tb = new TestBean("boring");
+ tb.setSpouse(testBean1);
+ return tb;
+ }
+
+ @Bean @Boring
+ public TestBean testBean2x() {
+ return new TestBean("boring");
+ }
+ }
+
+ @Configuration
+ static class FallbackConfig {
+
+ @Bean @Qualifier("interesting")
+ public static TestBean testBean1() {
+ return new TestBean("interesting");
+ }
+
+ @Bean @Qualifier("interesting") @Fallback
+ public static TestBean testBean1x() {
+ return new TestBean("interesting");
+ }
+
+ @Bean @Boring
+ public TestBean testBean2(TestBean testBean1) {
+ TestBean tb = new TestBean("boring");
+ tb.setSpouse(testBean1);
+ return tb;
+ }
+
+ @Bean @Boring @Fallback
+ public TestBean testBean2x() {
+ return new TestBean("boring");
+ }
+ }
+
@Component @Lazy
static class StandardPojo {