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.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
@ -155,6 +156,10 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether this dependency is required.
|
* 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() {
|
public boolean isRequired() {
|
||||||
if (!this.required) {
|
if (!this.required) {
|
||||||
|
|
@ -162,7 +167,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.field != null) {
|
if (this.field != null) {
|
||||||
return !(this.field.getType() == Optional.class ||
|
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
|
||||||
(kotlinPresent && KotlinDelegate.isNullable(this.field)));
|
(kotlinPresent && KotlinDelegate.isNullable(this.field)));
|
||||||
}
|
}
|
||||||
else {
|
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
|
* Return whether this dependency is 'eager' in the sense of
|
||||||
* eagerly resolving potential target beans for type matching.
|
* eagerly resolving potential target beans for type matching.
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.beans.factory.annotation;
|
package org.springframework.beans.factory.annotation;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -574,6 +576,60 @@ public class InjectAnnotationBeanPostProcessorTests {
|
||||||
bf.destroySingletons();
|
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
|
@Test
|
||||||
public void testOptionalFieldInjectionWithBeanAvailable() {
|
public void testOptionalFieldInjectionWithBeanAvailable() {
|
||||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
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 {
|
public static class OptionalFieldInjectionBean {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -1291,8 +1377,8 @@ public class InjectAnnotationBeanPostProcessorTests {
|
||||||
private Optional<TestBean> testBean;
|
private Optional<TestBean> testBean;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public void setTestBean(Optional<TestBean> testBeanFactory) {
|
public void setTestBean(Optional<TestBean> testBean) {
|
||||||
this.testBean = testBeanFactory;
|
this.testBean = testBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TestBean> getTestBean() {
|
public Optional<TestBean> getTestBean() {
|
||||||
|
|
@ -1317,8 +1403,8 @@ public class InjectAnnotationBeanPostProcessorTests {
|
||||||
private Optional<List<TestBean>> testBean;
|
private Optional<List<TestBean>> testBean;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public void setTestBean(Optional<List<TestBean>> testBeanFactory) {
|
public void setTestBean(Optional<List<TestBean>> testBean) {
|
||||||
this.testBean = testBeanFactory;
|
this.testBean = testBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<List<TestBean>> getTestBean() {
|
public Optional<List<TestBean>> getTestBean() {
|
||||||
|
|
@ -1343,8 +1429,8 @@ public class InjectAnnotationBeanPostProcessorTests {
|
||||||
private Provider<Optional<TestBean>> testBean;
|
private Provider<Optional<TestBean>> testBean;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public void setTestBean(Provider<Optional<TestBean>> testBeanFactory) {
|
public void setTestBean(Provider<Optional<TestBean>> testBean) {
|
||||||
this.testBean = testBeanFactory;
|
this.testBean = testBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TestBean> getTestBean() {
|
public Optional<TestBean> getTestBean() {
|
||||||
|
|
|
||||||
|
|
@ -322,16 +322,32 @@ public class MethodParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether this method indicates a parameter which is not required
|
* 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
|
* either in the form of Java 8's {@link java.util.Optional}, any variant
|
||||||
* nullable type).
|
* 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
|
* @since 4.3
|
||||||
*/
|
*/
|
||||||
public boolean isOptional() {
|
public boolean isOptional() {
|
||||||
return (getParameterType() == Optional.class ||
|
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
|
||||||
(kotlinPresent && KotlinDelegate.isNullable(this)));
|
(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
|
* Return a variant of this {@code MethodParameter} which points to
|
||||||
* the same parameter but one nesting level deeper in case of a
|
* the same parameter but one nesting level deeper in case of a
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue