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 0302a3129f..5bbed07d91 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-2009 the original author or authors. + * Copyright 2002-2012 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. @@ -174,8 +174,31 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan SimpleTypeConverter typeConverter = new SimpleTypeConverter(); for (Annotation annotation : annotationsToSearch) { Class type = annotation.annotationType(); + boolean checkMeta = true; + boolean fallbackToMeta = false; if (isQualifier(type)) { if (!checkQualifier(bdHolder, annotation, typeConverter)) { + fallbackToMeta = true; + } + else { + checkMeta = false; + } + } + if (checkMeta) { + boolean foundMeta = false; + for (Annotation metaAnn : type.getAnnotations()) { + Class metaType = metaAnn.annotationType(); + if (isQualifier(metaType)) { + foundMeta = true; + // Only accept fallback match if @Qualifier annotation has a value... + // Otherwise it is just a marker for a custom qualifier annotation. + if ((fallbackToMeta && AnnotationUtils.getValue(metaAnn) == null) || + !checkQualifier(bdHolder, metaAnn, typeConverter)) { + return false; + } + } + } + if (fallbackToMeta && !foundMeta) { return false; } } @@ -210,18 +233,18 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan if (qualifier == null) { Annotation targetAnnotation = null; if (bd.getResolvedFactoryMethod() != null) { - targetAnnotation = bd.getResolvedFactoryMethod().getAnnotation(type); + targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type); } if (targetAnnotation == null) { // look for matching annotation on the target class if (this.beanFactory != null) { Class beanType = this.beanFactory.getType(bdHolder.getBeanName()); if (beanType != null) { - targetAnnotation = ClassUtils.getUserClass(beanType).getAnnotation(type); + targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type); } } if (targetAnnotation == null && bd.hasBeanClass()) { - targetAnnotation = ClassUtils.getUserClass(bd.getBeanClass()).getAnnotation(type); + targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type); } } if (targetAnnotation != null && targetAnnotation.equals(annotation)) { @@ -286,14 +309,27 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan protected Object findValue(Annotation[] annotationsToSearch) { for (Annotation annotation : annotationsToSearch) { if (this.valueAnnotationType.isInstance(annotation)) { - Object value = AnnotationUtils.getValue(annotation); - if (value == null) { - throw new IllegalStateException("Value annotation must have a value attribute"); - } - return value; + return extractValue(annotation); + } + } + for (Annotation annotation : annotationsToSearch) { + Annotation metaAnn = annotation.annotationType().getAnnotation(this.valueAnnotationType); + if (metaAnn != null) { + return extractValue(metaAnn); } } return null; } + /** + * Extract the value attribute from the given annotation. + */ + protected Object extractValue(Annotation valueAnnotation) { + Object value = AnnotationUtils.getValue(valueAnnotation); + if (value == null) { + throw new IllegalStateException("Value annotation must have a value attribute"); + } + return value; + } + } diff --git a/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java b/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java index 8b246e93a4..b3fc360aa3 100644 --- a/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java +++ b/spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2012 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. @@ -21,7 +21,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import static org.junit.Assert.*; import org.junit.Test; import org.springframework.aop.scope.ScopedProxyUtils; @@ -35,6 +34,8 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; +import static org.junit.Assert.*; + /** * Integration tests for handling {@link Qualifier} annotations. * @@ -279,7 +280,7 @@ public class QualifierAnnotationAutowireContextTests { RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); context.registerBeanDefinition(JUERGEN, person1); context.registerBeanDefinition(MARK, person2); - context.registerBeanDefinition("autowired", + context.registerBeanDefinition("autowired", new RootBeanDefinition(QualifiedFieldTestBean.class)); AnnotationConfigUtils.registerAnnotationConfigProcessors(context); context.refresh(); @@ -287,6 +288,26 @@ public class QualifierAnnotationAutowireContextTests { assertEquals(JUERGEN, bean.getPerson().getName()); } + @Test + public void testAutowiredFieldResolvesMetaQualifiedCandidate() { + GenericApplicationContext context = new GenericApplicationContext(); + ConstructorArgumentValues cavs1 = new ConstructorArgumentValues(); + cavs1.addGenericArgumentValue(JUERGEN); + RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null); + person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class)); + ConstructorArgumentValues cavs2 = new ConstructorArgumentValues(); + cavs2.addGenericArgumentValue(MARK); + RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null); + context.registerBeanDefinition(JUERGEN, person1); + context.registerBeanDefinition(MARK, person2); + context.registerBeanDefinition("autowired", + new RootBeanDefinition(MetaQualifiedFieldTestBean.class)); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + MetaQualifiedFieldTestBean bean = (MetaQualifiedFieldTestBean) context.getBean("autowired"); + assertEquals(JUERGEN, bean.getPerson().getName()); + } + @Test public void testAutowiredMethodParameterResolvesQualifiedCandidate() { GenericApplicationContext context = new GenericApplicationContext(); @@ -596,6 +617,24 @@ public class QualifierAnnotationAutowireContextTests { } + private static class MetaQualifiedFieldTestBean { + + @MyAutowired + private Person person; + + public Person getPerson() { + return this.person; + } + } + + + @Autowired + @TestQualifier + @Retention(RetentionPolicy.RUNTIME) + public static @interface MyAutowired { + } + + private static class QualifiedMethodParameterTestBean { private Person person; @@ -706,7 +745,6 @@ public class QualifierAnnotationAutowireContextTests { } - @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public static @interface TestQualifier { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java index 3ead1c5b04..872cc9281a 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java @@ -1,52 +1,117 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.context.annotation.configuration; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import org.junit.Test; +import test.beans.TestBean; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import test.beans.TestBean; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; /** * Tests proving that @Qualifier annotations work when used * with @Configuration classes on @Bean methods. * * @author Chris Beams + * @author Juergen Hoeller */ public class BeanMethodQualificationTests { @Test - public void test() { - ApplicationContext ctx = - new AnnotationConfigApplicationContext(Config.class, Pojo.class); - Pojo pojo = ctx.getBean(Pojo.class); + public void testStandard() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(StandardConfig.class, StandardPojo.class); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean1")); + StandardPojo pojo = ctx.getBean(StandardPojo.class); assertThat(pojo.testBean.getName(), equalTo("interesting")); } - + + @Test + public void testCustom() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(CustomConfig.class, CustomPojo.class); + assertFalse(ctx.getBeanFactory().containsSingleton("testBean1")); + CustomPojo pojo = ctx.getBean(CustomPojo.class); + assertThat(pojo.testBean.getName(), equalTo("interesting")); + } + + @Configuration - static class Config { - @Bean - @Qualifier("interesting") + static class StandardConfig { + @Bean @Lazy @Qualifier("interesting") public TestBean testBean1() { return new TestBean("interesting"); } - @Bean - @Qualifier("boring") + @Bean @Qualifier("boring") public TestBean testBean2() { return new TestBean("boring"); } } - - @Component - static class Pojo { + + @Component @Lazy + static class StandardPojo { @Autowired @Qualifier("interesting") TestBean testBean; } + + @Configuration + static class CustomConfig { + @InterestingBean + public TestBean testBean1() { + return new TestBean("interesting"); + } + + @Bean @Qualifier("boring") + public TestBean testBean2() { + return new TestBean("boring"); + } + } + + @InterestingPojo + static class CustomPojo { + @InterestingNeed TestBean testBean; + } + + @Bean @Lazy @Qualifier("interesting") + @Retention(RetentionPolicy.RUNTIME) + public @interface InterestingBean { + } + + @Autowired @Qualifier("interesting") + @Retention(RetentionPolicy.RUNTIME) + public @interface InterestingNeed { + } + + @Component @Lazy + @Retention(RetentionPolicy.RUNTIME) + public @interface InterestingPojo { + } + }