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 03fbb2218ed..f4c19b61710 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 @@ -17,6 +17,7 @@ package org.springframework.beans.factory.annotation; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.LinkedHashSet; import java.util.Map; @@ -49,6 +50,7 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher * @author Juergen Hoeller + * @author Stephane Nicoll * @since 2.5 * @see AutowireCandidateQualifier * @see Qualifier @@ -225,8 +227,12 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa qualifier = bd.getQualifier(ClassUtils.getShortName(type)); } if (qualifier == null) { - // First, check annotation on factory method, if applicable - Annotation targetAnnotation = getFactoryMethodAnnotation(bd, type); + // First, check annotation on qualified element, if any + Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type); + // Then, check annotation on factory method, if applicable + if (targetAnnotation == null) { + targetAnnotation = getFactoryMethodAnnotation(bd, type); + } if (targetAnnotation == null) { RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd); if (dbd != null) { @@ -291,6 +297,11 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa return true; } + protected Annotation getQualifiedElementAnnotation(RootBeanDefinition bd, Class type) { + AnnotatedElement qualifiedElement = bd.getQualifiedElement(); + return (qualifiedElement != null ? AnnotationUtils.getAnnotation(qualifiedElement, type) : null); + } + protected Annotation getFactoryMethodAnnotation(RootBeanDefinition bd, Class type) { Method resolvedFactoryMethod = bd.getResolvedFactoryMethod(); return (resolvedFactoryMethod != null ? AnnotationUtils.getAnnotation(resolvedFactoryMethod, type) : null); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index a8f117c20da..a5a0abcc01c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -16,6 +16,7 @@ package org.springframework.beans.factory.support; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Executable; import java.lang.reflect.Member; import java.lang.reflect.Method; @@ -58,6 +59,8 @@ public class RootBeanDefinition extends AbstractBeanDefinition { boolean isFactoryMethodUnique = false; + volatile AnnotatedElement qualifiedElement; + /** Package-visible field for caching the determined Class of a given bean definition */ volatile Class resolvedTargetType; @@ -182,6 +185,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { this.allowCaching = original.allowCaching; this.targetType = original.targetType; this.isFactoryMethodUnique = original.isFactoryMethodUnique; + this.qualifiedElement = original.qualifiedElement; } /** @@ -257,6 +261,22 @@ public class RootBeanDefinition extends AbstractBeanDefinition { this.isFactoryMethodUnique = true; } + /** + * Specify the {@link AnnotatedElement} defining qualifiers. + * @since 4.3.3 + */ + public void setQualifiedElement(AnnotatedElement qualifiedElement) { + this.qualifiedElement = qualifiedElement; + } + + /** + * Return the {@link AnnotatedElement} defining qualifiers, if any. + * @since 4.3.3 + */ + public AnnotatedElement getQualifiedElement() { + return this.qualifiedElement; + } + /** * Check whether the given candidate qualifies as a factory method. */ 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 db4e891e13b..16f578f0057 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 @@ -63,6 +63,7 @@ import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.IndexedTestBean; import org.springframework.tests.sample.beans.NestedTestBean; import org.springframework.tests.sample.beans.TestBean; +import org.springframework.util.ReflectionUtils; import org.springframework.util.SerializationTestUtils; import static org.junit.Assert.*; @@ -1026,14 +1027,35 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); bd.addQualifier(new AutowireCandidateQualifier(Qualifier.class, "testBean")); - bf.registerBeanDefinition("testBean", bd); - bf.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean.class)); + bf.registerBeanDefinition("dependencyBean", bd); + bf.registerBeanDefinition("dependencyBean2", new RootBeanDefinition(TestBean.class)); ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); - assertSame(bf.getBean("testBean"), bean.getTestBean()); + assertSame(bf.getBean("dependencyBean"), bean.getTestBean()); + bf.destroySingletons(); + } + + @Test + public void testObjectFactoryQualifierProviderInjection() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectFactoryQualifierInjectionBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setQualifiedElement(ReflectionUtils.findMethod(getClass(), "testBeanQualifierProvider")); + bf.registerBeanDefinition("dependencyBean", bd); + bf.registerBeanDefinition("dependencyBean2", new RootBeanDefinition(TestBean.class)); + + ObjectFactoryQualifierInjectionBean bean = (ObjectFactoryQualifierInjectionBean) bf.getBean("annotatedBean"); + assertSame(bf.getBean("dependencyBean"), bean.getTestBean()); bf.destroySingletons(); } + @Qualifier("testBean") + private void testBeanQualifierProvider() {} + @Test public void testObjectFactorySerialization() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -1588,11 +1610,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { rbd.setFactoryBeanName("mocksControl"); rbd.setFactoryMethodName("createMock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Repository.class); - bf.registerBeanDefinition("integerRepo", rbd); + rbd.setQualifiedElement(ReflectionUtils.findField(getClass(), "integerRepositoryQualifierProvider")); + bf.registerBeanDefinition("integerRepository", rbd); // Bean name not matching qualifier RepositoryFieldInjectionBeanWithQualifiers bean = (RepositoryFieldInjectionBeanWithQualifiers) bf.getBean("annotatedBean"); Repository sr = bf.getBean("stringRepo", Repository.class); - Repository ir = bf.getBean("integerRepo", Repository.class); + Repository ir = bf.getBean("integerRepository", Repository.class); assertSame(sr, bean.stringRepository); assertSame(ir, bean.integerRepository); assertSame(1, bean.stringRepositoryArray.length); @@ -1606,9 +1629,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(1, bean.stringRepositoryMap.size()); assertSame(1, bean.integerRepositoryMap.size()); assertSame(sr, bean.stringRepositoryMap.get("stringRepo")); - assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); + assertSame(ir, bean.integerRepositoryMap.get("integerRepository")); } + @Qualifier("integerRepo") + private Repository integerRepositoryQualifierProvider; + @Test public void testGenericsBasedFieldInjectionWithSimpleMatch() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java index 899bee05526..9e32012468a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanDefinitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -126,6 +126,7 @@ public class BeanDefinitionTests { bd.getConstructorArgumentValues().addIndexedArgumentValue(1, new Integer(5)); bd.getPropertyValues().add("name", "myName"); bd.getPropertyValues().add("age", "99"); + bd.setQualifiedElement(getClass()); GenericBeanDefinition childBd = new GenericBeanDefinition(); childBd.setParentName("bd"); @@ -138,6 +139,7 @@ public class BeanDefinitionTests { mergedBd.getConstructorArgumentValues().getArgumentValue(1, null).setValue(new Integer(9)); assertEquals(new Integer(5), bd.getConstructorArgumentValues().getArgumentValue(1, null).getValue()); + assertEquals(getClass(), bd.getQualifiedElement()); } }