From d74542ed21b5a6c2b93547c73e86e1a85954ca9d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 3 May 2017 13:42:16 +0200 Subject: [PATCH] Honor @Autowired(required=false) at parameter level Includes a revision of the AutowireCandidateResolver SPI with default methods. Issue: SPR-15268 --- .../beans/factory/InjectionPoint.java | 13 +++- ...erAnnotationAutowireCandidateResolver.java | 18 ++++- .../support/AutowireCandidateResolver.java | 32 +++++++-- .../support/DefaultListableBeanFactory.java | 8 ++- ...ricTypeAwareAutowireCandidateResolver.java | 28 ++------ .../SimpleAutowireCandidateResolver.java | 14 ++-- ...wiredAnnotationBeanPostProcessorTests.java | 65 ++++++++++++++++++- 7 files changed, 136 insertions(+), 42 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java index 0a3d55a9312..38ace23fd40 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,6 +111,17 @@ public class InjectionPoint { } } + /** + * Retrieve a field/parameter annotation of the given type, if any. + * @param annotationType the annotation type to retrieve + * @return the annotation instance, or {@code null} if none found + * @since 4.3.9 + */ + public A getAnnotation(Class annotationType) { + return (this.field != null ? this.field.getAnnotation(annotationType) : + this.methodParameter.getParameterAnnotation(annotationType)); + } + /** * Return the type declared by the underlying field or method/constructor parameter, * indicating the injection type. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index f4c19b61710..46c42853c10 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -309,7 +309,21 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa /** - * Determine whether the given dependency carries a value annotation. + * Determine whether the given dependency declares an autowired annotation, + * checking its required flag. + * @see Autowired#required() + */ + @Override + public boolean isRequired(DependencyDescriptor descriptor) { + if (!super.isRequired(descriptor)) { + return false; + } + Autowired autowired = descriptor.getAnnotation(Autowired.class); + return (autowired == null || autowired.required()); + } + + /** + * Determine whether the given dependency declares a value annotation. * @see Value */ @Override diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java index 2c3e15fcc8b..a1e2eaf9f14 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,30 +32,54 @@ public interface AutowireCandidateResolver { /** * Determine whether the given bean definition qualifies as an * autowire candidate for the given dependency. + *

The default implementation checks + * {@link org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()}. * @param bdHolder the bean definition including bean name and aliases * @param descriptor the descriptor for the target method parameter or field * @return whether the bean definition qualifies as autowire candidate + * @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate() */ - boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor); + default boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { + return bdHolder.getBeanDefinition().isAutowireCandidate(); + } + + /** + * Determine whether the given descriptor is effectively required. + *

The default implementation checks {@link DependencyDescriptor#isRequired()}. + * @param descriptor the descriptor for the target method parameter or field + * @return whether the descriptor is marked as required or possibly indicating + * non-required status some other way (e.g. through a parameter annotation) + * @since 5.0 + * @see DependencyDescriptor#isRequired() + */ + default boolean isRequired(DependencyDescriptor descriptor) { + return descriptor.isRequired(); + } /** * Determine whether a default value is suggested for the given dependency. + *

The default implementation simply returns {@code null}. * @param descriptor the descriptor for the target method parameter or field * @return the value suggested (typically an expression String), * or {@code null} if none found * @since 3.0 */ - Object getSuggestedValue(DependencyDescriptor descriptor); + default Object getSuggestedValue(DependencyDescriptor descriptor) { + return null; + } /** * Build a proxy for lazy resolution of the actual dependency target, * if demanded by the injection point. + *

The default implementation simply returns {@code null}. * @param descriptor the descriptor for the target method parameter or field * @param beanName the name of the bean that contains the injection point * @return the lazy resolution proxy for the actual dependency target, * or {@code null} if straight resolution is to be performed * @since 4.0 */ - Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName); + default Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return null; + } } 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 7d80043aeb0..7daa3e6a23c 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 @@ -1090,7 +1090,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { + if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } return null; @@ -1102,7 +1102,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { - if (descriptor.isRequired() || !indicatesMultipleBeans(type)) { + if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(type, matchingBeans); } else { @@ -1207,6 +1207,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } + private boolean isRequired(DependencyDescriptor descriptor) { + return this.autowireCandidateResolver.isRequired(descriptor); + } + private boolean indicatesMultipleBeans(Class type) { return (type.isArray() || (type.isInterface() && (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)))); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index b204d551c3c..7ffbed3e0e5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,8 @@ import org.springframework.util.ClassUtils; * @author Juergen Hoeller * @since 4.0 */ -public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandidateResolver, BeanFactoryAware { +public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCandidateResolver + implements BeanFactoryAware { private BeanFactory beanFactory; @@ -57,8 +58,8 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid @Override public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { - if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { - // if explicitly false, do not proceed with any other checks + if (!super.isAutowireCandidate(bdHolder, descriptor)) { + // If explicitly false, do not proceed with any other checks... return false; } return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor)); @@ -166,23 +167,4 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid return null; } - - /** - * This implementation always returns {@code null}, leaving suggested value support up - * to subclasses. - */ - @Override - public Object getSuggestedValue(DependencyDescriptor descriptor) { - return null; - } - - /** - * This implementation always returns {@code null}, leaving lazy resolution support up - * to subclasses. - */ - @Override - public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { - return null; - } - } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java index 4d29b95b6e2..b22bd1470d9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.beans.factory.support; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; @@ -27,20 +26,19 @@ import org.springframework.beans.factory.config.DependencyDescriptor; * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 - * @see BeanDefinition#isAutowireCandidate() */ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolver { - /** - * Determine if the provided bean definition is an autowire candidate. - *

To be considered a candidate the bean's autowire-candidate - * attribute must not have been set to 'false'. - */ @Override public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { return bdHolder.getBeanDefinition().isAutowireCandidate(); } + @Override + public boolean isRequired(DependencyDescriptor descriptor) { + return descriptor.isRequired(); + } + @Override public Object getSuggestedValue(DependencyDescriptor descriptor) { return null; 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 17ee5a3ac51..08d3c8587fa 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 @@ -699,8 +699,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(bf); bf.addBeanPostProcessor(bpp); - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition( - ConstructorsCollectionResourceInjectionBean.class)); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ConstructorsCollectionResourceInjectionBean.class)); TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); @@ -717,6 +716,46 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.destroySingletons(); } + @Test + public void testSingleConstructorInjectionWithMultipleCandidatesAsOrderedCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + FixedOrder2NestedTestBean ntb1 = new FixedOrder2NestedTestBean(); + bf.registerSingleton("nestedTestBean1", ntb1); + FixedOrder1NestedTestBean ntb2 = new FixedOrder1NestedTestBean(); + bf.registerSingleton("nestedTestBean2", ntb2); + + SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertEquals(2, bean.getNestedTestBeans().size()); + assertSame(ntb2, bean.getNestedTestBeans().get(0)); + assertSame(ntb1, bean.getNestedTestBeans().get(1)); + bf.destroySingletons(); + } + + @Test + public void testSingleConstructorInjectionWithEmptyCollection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SingleConstructorCollectionInjectionBean.class)); + TestBean tb = new TestBean(); + bf.registerSingleton("testBean", tb); + + SingleConstructorCollectionInjectionBean bean = (SingleConstructorCollectionInjectionBean) bf.getBean("annotatedBean"); + assertSame(tb, bean.getTestBean()); + assertNull(bean.getNestedTestBeans()); + bf.destroySingletons(); + } + @Test public void testConstructorResourceInjectionWithMultipleCandidatesAndFallback() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -2757,6 +2796,28 @@ public class AutowiredAnnotationBeanPostProcessorTests { } + public static class SingleConstructorCollectionInjectionBean { + + private ITestBean testBean; + + private List nestedTestBeans; + + public SingleConstructorCollectionInjectionBean(ITestBean testBean, + @Autowired(required = false) List nestedTestBeans) { + this.testBean = testBean; + this.nestedTestBeans = nestedTestBeans; + } + + public ITestBean getTestBean() { + return this.testBean; + } + + public List getNestedTestBeans() { + return this.nestedTestBeans; + } + } + + @SuppressWarnings("serial") public static class MyTestBeanMap extends LinkedHashMap { }