From b5d153febf7395658a53c2fac08f342781b6cc8f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 27 May 2025 10:25:50 +0200 Subject: [PATCH] Enforce consistent null check on injection points for pre-existing singleton beans Includes consistent JSpecify nullness check on fields as optional injection points Closes gh-34952 See gh-34261 --- .../factory/config/DependencyDescriptor.java | 3 +- .../support/DefaultListableBeanFactory.java | 7 +--- ...wiredAnnotationBeanPostProcessorTests.java | 36 ++++++++++--------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 41c8a815da..de62d5e7ab 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -34,6 +34,7 @@ import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; +import org.springframework.core.Nullness; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; @@ -162,7 +163,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable } if (this.field != null) { - return !(this.field.getType() == Optional.class || hasNullableAnnotation() || + return !(this.field.getType() == Optional.class || Nullness.forField(this.field) == Nullness.NULLABLE || (KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field))); } else { 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 52a1479f72..93c176a662 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 @@ -1656,12 +1656,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (autowiredBeanNames != null) { autowiredBeanNames.add(dependencyName); } - boolean preExisting = containsSingleton(dependencyName); Object dependencyBean = getBean(dependencyName); - if (preExisting && dependencyBean instanceof NullBean) { - // for backwards compatibility with addCandidateEntry in the regular code path - dependencyBean = null; - } return resolveInstance(dependencyBean, descriptor, type, dependencyName); } } @@ -1972,7 +1967,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor streamDescriptor && streamDescriptor.isOrdered())) { Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); - candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance)); + candidates.put(candidateName, beanInstance); } else { candidates.put(candidateName, getType(candidateName)); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 6b51e6dd4a..2c9db160b2 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -974,7 +974,9 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("testBean", tb); bf.getBean("testBean"); - assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull(); + assertThatExceptionOfType(UnsatisfiedDependencyException.class) + .isThrownBy(() -> bf.getBean("annotatedBean")) + .satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class)); } @Test @@ -985,7 +987,9 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("testBean3", tb); bf.getBean("testBean3"); - assertThat(bf.getBean("annotatedBean", ConstructorWithoutFallbackBean.class).getTestBean3()).isNull(); + assertThatExceptionOfType(UnsatisfiedDependencyException.class) + .isThrownBy(() -> bf.getBean("annotatedBean")) + .satisfies(methodParameterDeclaredOn(ConstructorWithoutFallbackBean.class)); } @Test @@ -2733,22 +2737,22 @@ class AutowiredAnnotationBeanPostProcessorTests { public static class ResourceInjectionBean { @Autowired(required = false) - private TestBean testBean; + private @Nullable TestBean testBean; - TestBean testBean2; + @Nullable TestBean testBean2; @Autowired - public void setTestBean2(TestBean testBean2) { + public void setTestBean2(@Nullable TestBean testBean2) { Assert.state(this.testBean != null, "Wrong initialization order"); Assert.state(this.testBean2 == null, "Already called"); this.testBean2 = testBean2; } - public TestBean getTestBean() { + public @Nullable TestBean getTestBean() { return this.testBean; } - public TestBean getTestBean2() { + public @Nullable TestBean getTestBean2() { return this.testBean2; } } @@ -2757,13 +2761,13 @@ class AutowiredAnnotationBeanPostProcessorTests { static class NonPublicResourceInjectionBean extends ResourceInjectionBean { @Autowired - public final ITestBean testBean3 = null; + public final @Nullable ITestBean testBean3 = null; - private T nestedTestBean; + private @Nullable T nestedTestBean; - private ITestBean testBean4; + private @Nullable ITestBean testBean4; - protected BeanFactory beanFactory; + protected @Nullable BeanFactory beanFactory; public boolean baseInjected = false; @@ -2772,18 +2776,18 @@ class AutowiredAnnotationBeanPostProcessorTests { @Override @Autowired - public void setTestBean2(TestBean testBean2) { + public void setTestBean2(@Nullable TestBean testBean2) { this.testBean2 = testBean2; } @Autowired - private void inject(ITestBean testBean4, T nestedTestBean) { + private void inject(@Nullable ITestBean testBean4, @Nullable T nestedTestBean) { this.testBean4 = testBean4; this.nestedTestBean = nestedTestBean; } @Autowired - private void inject(ITestBean testBean4) { + private void inject(@Nullable ITestBean testBean4) { this.baseInjected = true; } @@ -2793,11 +2797,11 @@ class AutowiredAnnotationBeanPostProcessorTests { this.beanFactory = beanFactory; } - public ITestBean getTestBean3() { + public @Nullable ITestBean getTestBean3() { return this.testBean3; } - public ITestBean getTestBean4() { + public @Nullable ITestBean getTestBean4() { return this.testBean4; }