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 13a469be01e..c7321ad40ff 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 @@ -19,6 +19,7 @@ package org.springframework.beans.factory.config; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -155,6 +156,10 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable /** * Return whether this dependency is required. + *
Optional semantics are derived from Java 8's {@link java.util.Optional},
+ * any variant of a parameter-level {@code Nullable} annotation (such as from
+ * JSR-305 or the FindBugs set of annotations), or a language-level nullable
+ * type declaration in Kotlin.
*/
public boolean isRequired() {
if (!this.required) {
@@ -162,7 +167,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
}
if (this.field != null) {
- return !(this.field.getType() == Optional.class ||
+ return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
(kotlinPresent && KotlinDelegate.isNullable(this.field)));
}
else {
@@ -170,6 +175,20 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
}
}
+ /**
+ * Check whether the underlying field is annotated with any variant of a
+ * {@code Nullable} annotation, e.g. {@code javax.annotation.Nullable} or
+ * {@code edu.umd.cs.findbugs.annotations.Nullable}.
+ */
+ private boolean hasNullableAnnotation() {
+ for (Annotation ann : getAnnotations()) {
+ if ("Nullable".equals(ann.annotationType().getSimpleName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Return whether this dependency is 'eager' in the sense of
* eagerly resolving potential target beans for type matching.
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java
index 63390cb8ca7..9bcac1203c3 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java
@@ -17,6 +17,8 @@
package org.springframework.beans.factory.annotation;
import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -574,6 +576,60 @@ public class InjectAnnotationBeanPostProcessorTests {
bf.destroySingletons();
}
+ @Test
+ public void testNullableFieldInjectionWithBeanAvailable() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(NullableFieldInjectionBean.class));
+ bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
+
+ NullableFieldInjectionBean bean = (NullableFieldInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("testBean"), bean.getTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testNullableFieldInjectionWithBeanNotAvailable() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(NullableFieldInjectionBean.class));
+
+ NullableFieldInjectionBean bean = (NullableFieldInjectionBean) bf.getBean("annotatedBean");
+ assertNull(bean.getTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testNullableMethodInjectionWithBeanAvailable() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(NullableMethodInjectionBean.class));
+ bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
+
+ NullableMethodInjectionBean bean = (NullableMethodInjectionBean) bf.getBean("annotatedBean");
+ assertSame(bf.getBean("testBean"), bean.getTestBean());
+ bf.destroySingletons();
+ }
+
+ @Test
+ public void testNullableMethodInjectionWithBeanNotAvailable() {
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
+ bpp.setBeanFactory(bf);
+ bf.addBeanPostProcessor(bpp);
+ bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(NullableMethodInjectionBean.class));
+
+ NullableMethodInjectionBean bean = (NullableMethodInjectionBean) bf.getBean("annotatedBean");
+ assertNull(bean.getTestBean());
+ bf.destroySingletons();
+ }
+
@Test
public void testOptionalFieldInjectionWithBeanAvailable() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -1275,6 +1331,36 @@ public class InjectAnnotationBeanPostProcessorTests {
}
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Nullable {}
+
+
+ public static class NullableFieldInjectionBean {
+
+ @Inject @Nullable
+ private TestBean testBean;
+
+ public TestBean getTestBean() {
+ return this.testBean;
+ }
+ }
+
+
+ public static class NullableMethodInjectionBean {
+
+ private TestBean testBean;
+
+ @Inject
+ public void setTestBean(@Nullable TestBean testBean) {
+ this.testBean = testBean;
+ }
+
+ public TestBean getTestBean() {
+ return this.testBean;
+ }
+ }
+
+
public static class OptionalFieldInjectionBean {
@Inject
@@ -1291,8 +1377,8 @@ public class InjectAnnotationBeanPostProcessorTests {
private Optional> testBean;
@Inject
- public void setTestBean(Optional
> testBeanFactory) {
- this.testBean = testBeanFactory;
+ public void setTestBean(Optional
> testBean) {
+ this.testBean = testBean;
}
public Optional
> getTestBean() {
@@ -1343,8 +1429,8 @@ public class InjectAnnotationBeanPostProcessorTests {
private Provider