From 5877a38fa1a233318f2900f32cefbcec5018653d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 8 Mar 2025 12:21:46 +0100 Subject: [PATCH 1/3] Add explicit note on JSpecify support in Spring Framework 6.2 vs 7.0 Closes gh-34551 --- .../core/beans/annotation-config/autowired.adoc | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc index 8f99eac7d2f..015e73b748f 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/autowired.adoc @@ -444,9 +444,9 @@ through Java 8's `java.util.Optional`, as the following example shows: } ---- -You can also use a `@Nullable` annotation (of any kind in any package -- for example, -`javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in null-safety -support: +You can also use a parameter-level `@Nullable` annotation (of any kind in any package -- +for example, `javax.annotation.Nullable` from JSR-305) or just leverage Kotlin built-in +null-safety support: [tabs] ====== @@ -477,6 +477,13 @@ Kotlin:: ---- ====== +[NOTE] +==== +A type-level `@Nullable` annotation such as from JSpecify is not supported in Spring +Framework 6.2 yet. You need to upgrade to Spring Framework 7.0 where the framework +detects type-level annotations and consistently declares JSpecify in its own codebase. +==== + You can also use `@Autowired` for interfaces that are well-known resolvable dependencies: `BeanFactory`, `ApplicationContext`, `Environment`, `ResourceLoader`, `ApplicationEventPublisher`, and `MessageSource`. These interfaces and their extended From 4bd280b87e76cae9c7a7f2dd8c4837c119e4f328 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 8 Mar 2025 12:22:04 +0100 Subject: [PATCH 2/3] Explain availability and uniqueness (including primary/fallback/default) in javadoc Closes gh-34447 --- .../beans/factory/ObjectProvider.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java index 492d224a9cb..d62c3ee6d3c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -46,6 +46,21 @@ import org.springframework.lang.Nullable; * Alternatively, you may implement the specific methods that your callers expect, * for example, just {@link #getObject()} or {@link #getIfAvailable()}. * + *

Note that {@link #getObject()} never returns {@code null} - it will throw a + * {@link NoSuchBeanDefinitionException} instead -, whereas {@link #getIfAvailable()} + * will return {@code null} if no matching bean is present at all. However, both + * methods will throw a {@link NoUniqueBeanDefinitionException} if more than one + * matching bean is found without a clear unique winner (see below). Last but not + * least, {@link #getIfUnique()} will return {@code null} both when no matching bean + * is found and when more than one matching bean is found without a unique winner. + * + *

Uniqueness is generally up to the container's candidate resolution algorithm + * but always honors the "primary" flag (with only one of the candidate beans marked + * as primary) and the "fallback" flag (with only one of the candidate beans not + * marked as fallback). The default-candidate flag is consistently taken into + * account as well, even for non-annotation-based injection points, with a single + * default candidate winning in case of no clear primary/fallback indication. + * * @author Juergen Hoeller * @since 4.3 * @param the object type @@ -188,7 +203,7 @@ public interface ObjectProvider extends ObjectFactory, Iterable { * if unique (not called otherwise) * @throws BeansException in case of creation errors * @since 5.0 - * @see #getIfAvailable() + * @see #getIfUnique() */ default void ifUnique(Consumer dependencyConsumer) throws BeansException { T dependency = getIfUnique(); From 143985e8627d1630618c68a7e62c461917625106 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 8 Mar 2025 12:22:14 +0100 Subject: [PATCH 3/3] Add tests for primary/fallback/defaultCandidate precedence Closes gh-34449 --- .../DefaultListableBeanFactoryTests.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 244f67a53c2..4238da225ea 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -113,6 +113,7 @@ import static org.mockito.Mockito.verify; * @author Chris Beams * @author Phillip Webb * @author Stephane Nicoll + * @author Yanming Zhou */ class DefaultListableBeanFactoryTests { @@ -1665,6 +1666,60 @@ class DefaultListableBeanFactoryTests { assertThat(lbf.containsSingleton("bd1")).isFalse(); } + @Test + void getBeanByTypeWithUniqueNonFallbackDefinition() { + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + bd1.setLazyInit(true); + bd1.setFallback(true); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setFallback(true); + RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + lbf.registerBeanDefinition("bd3", bd3); + + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName()).isEqualTo("bd3"); + assertThat(lbf.containsSingleton("bd1")).isFalse(); + } + + @Test + void getBeanByTypeWithPrimaryAndUniqueNonFallbackDefinition() { + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + bd1.setLazyInit(true); + bd1.setFallback(true); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setPrimary(true); + bd2.setFallback(true); + RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + lbf.registerBeanDefinition("bd3", bd3); + + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName()).isEqualTo("bd2"); + assertThat(lbf.containsSingleton("bd1")).isFalse(); + } + + @Test + void getBeanByTypeWithUniqueNonFallbackAndUniqueNonDefaultDefinition() { + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + bd1.setLazyInit(true); + bd1.setFallback(true); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setFallback(true); + bd2.setDefaultCandidate(false); + RootBeanDefinition bd3 = new RootBeanDefinition(TestBean.class); + bd3.setDefaultCandidate(false); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + lbf.registerBeanDefinition("bd3", bd3); + + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName()).isEqualTo("bd3"); + assertThat(lbf.containsSingleton("bd1")).isFalse(); + } + @Test void getBeanByTypeWithUniqueNonDefaultDefinition() { RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class);