Support @Nullable annotations as indicators for optional injection points
Issue: SPR-15028
This commit is contained in:
parent
8be791c4ff
commit
12aa14ddbc
|
|
@ -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.
|
||||
* <p>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.
|
||||
|
|
|
|||
|
|
@ -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> testBean;
|
||||
|
||||
@Inject
|
||||
public void setTestBean(Optional<TestBean> testBeanFactory) {
|
||||
this.testBean = testBeanFactory;
|
||||
public void setTestBean(Optional<TestBean> testBean) {
|
||||
this.testBean = testBean;
|
||||
}
|
||||
|
||||
public Optional<TestBean> getTestBean() {
|
||||
|
|
@ -1317,8 +1403,8 @@ public class InjectAnnotationBeanPostProcessorTests {
|
|||
private Optional<List<TestBean>> testBean;
|
||||
|
||||
@Inject
|
||||
public void setTestBean(Optional<List<TestBean>> testBeanFactory) {
|
||||
this.testBean = testBeanFactory;
|
||||
public void setTestBean(Optional<List<TestBean>> testBean) {
|
||||
this.testBean = testBean;
|
||||
}
|
||||
|
||||
public Optional<List<TestBean>> getTestBean() {
|
||||
|
|
@ -1343,8 +1429,8 @@ public class InjectAnnotationBeanPostProcessorTests {
|
|||
private Provider<Optional<TestBean>> testBean;
|
||||
|
||||
@Inject
|
||||
public void setTestBean(Provider<Optional<TestBean>> testBeanFactory) {
|
||||
this.testBean = testBeanFactory;
|
||||
public void setTestBean(Provider<Optional<TestBean>> testBean) {
|
||||
this.testBean = testBean;
|
||||
}
|
||||
|
||||
public Optional<TestBean> getTestBean() {
|
||||
|
|
|
|||
|
|
@ -322,16 +322,32 @@ public class MethodParameter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether this method indicates a parameter which is not required
|
||||
* (either in the form of Java 8's {@link java.util.Optional} or Kotlin's
|
||||
* nullable type).
|
||||
* Return whether this method indicates a parameter which is not required:
|
||||
* either in the form of 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.
|
||||
* @since 4.3
|
||||
*/
|
||||
public boolean isOptional() {
|
||||
return (getParameterType() == Optional.class ||
|
||||
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
|
||||
(kotlinPresent && KotlinDelegate.isNullable(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this method parameter 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 : getParameterAnnotations()) {
|
||||
if ("Nullable".equals(ann.annotationType().getSimpleName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a variant of this {@code MethodParameter} which points to
|
||||
* the same parameter but one nesting level deeper in case of a
|
||||
|
|
|
|||
Loading…
Reference in New Issue