Browse Source

Support @Nullable annotations as indicators for optional injection points

Issue: SPR-15028
pull/1274/head
Juergen Hoeller 8 years ago
parent
commit
12aa14ddbc
  1. 21
      spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java
  2. 98
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java
  3. 24
      spring-core/src/main/java/org/springframework/core/MethodParameter.java

21
spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

@ -19,6 +19,7 @@ package org.springframework.beans.factory.config; @@ -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 @@ -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 @@ -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 @@ -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.

98
spring-beans/src/test/java/org/springframework/beans/factory/annotation/InjectAnnotationBeanPostProcessorTests.java

@ -17,6 +17,8 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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() {

24
spring-core/src/main/java/org/springframework/core/MethodParameter.java

@ -322,16 +322,32 @@ public class MethodParameter { @@ -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…
Cancel
Save