Skip to content

Commit 914a1b2

Browse files
committed
@Autowired, @value and qualifiers may be used as meta-annotations for custom injection annotations
1 parent 985cb9d commit 914a1b2

File tree

3 files changed

+168
-29
lines changed

3 files changed

+168
-29
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -174,8 +174,31 @@ protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] an
174174
SimpleTypeConverter typeConverter = new SimpleTypeConverter();
175175
for (Annotation annotation : annotationsToSearch) {
176176
Class<? extends Annotation> type = annotation.annotationType();
177+
boolean checkMeta = true;
178+
boolean fallbackToMeta = false;
177179
if (isQualifier(type)) {
178180
if (!checkQualifier(bdHolder, annotation, typeConverter)) {
181+
fallbackToMeta = true;
182+
}
183+
else {
184+
checkMeta = false;
185+
}
186+
}
187+
if (checkMeta) {
188+
boolean foundMeta = false;
189+
for (Annotation metaAnn : type.getAnnotations()) {
190+
Class<? extends Annotation> metaType = metaAnn.annotationType();
191+
if (isQualifier(metaType)) {
192+
foundMeta = true;
193+
// Only accept fallback match if @Qualifier annotation has a value...
194+
// Otherwise it is just a marker for a custom qualifier annotation.
195+
if ((fallbackToMeta && AnnotationUtils.getValue(metaAnn) == null) ||
196+
!checkQualifier(bdHolder, metaAnn, typeConverter)) {
197+
return false;
198+
}
199+
}
200+
}
201+
if (fallbackToMeta && !foundMeta) {
179202
return false;
180203
}
181204
}
@@ -210,18 +233,18 @@ protected boolean checkQualifier(
210233
if (qualifier == null) {
211234
Annotation targetAnnotation = null;
212235
if (bd.getResolvedFactoryMethod() != null) {
213-
targetAnnotation = bd.getResolvedFactoryMethod().getAnnotation(type);
236+
targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type);
214237
}
215238
if (targetAnnotation == null) {
216239
// look for matching annotation on the target class
217240
if (this.beanFactory != null) {
218241
Class<?> beanType = this.beanFactory.getType(bdHolder.getBeanName());
219242
if (beanType != null) {
220-
targetAnnotation = ClassUtils.getUserClass(beanType).getAnnotation(type);
243+
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
221244
}
222245
}
223246
if (targetAnnotation == null && bd.hasBeanClass()) {
224-
targetAnnotation = ClassUtils.getUserClass(bd.getBeanClass()).getAnnotation(type);
247+
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
225248
}
226249
}
227250
if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
@@ -286,14 +309,27 @@ public Object getSuggestedValue(DependencyDescriptor descriptor) {
286309
protected Object findValue(Annotation[] annotationsToSearch) {
287310
for (Annotation annotation : annotationsToSearch) {
288311
if (this.valueAnnotationType.isInstance(annotation)) {
289-
Object value = AnnotationUtils.getValue(annotation);
290-
if (value == null) {
291-
throw new IllegalStateException("Value annotation must have a value attribute");
292-
}
293-
return value;
312+
return extractValue(annotation);
313+
}
314+
}
315+
for (Annotation annotation : annotationsToSearch) {
316+
Annotation metaAnn = annotation.annotationType().getAnnotation(this.valueAnnotationType);
317+
if (metaAnn != null) {
318+
return extractValue(metaAnn);
294319
}
295320
}
296321
return null;
297322
}
298323

324+
/**
325+
* Extract the value attribute from the given annotation.
326+
*/
327+
protected Object extractValue(Annotation valueAnnotation) {
328+
Object value = AnnotationUtils.getValue(valueAnnotation);
329+
if (value == null) {
330+
throw new IllegalStateException("Value annotation must have a value attribute");
331+
}
332+
return value;
333+
}
334+
299335
}

spring-context/src/test/java/org/springframework/beans/factory/support/QualifierAnnotationAutowireContextTests.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2007 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
2121
import java.lang.annotation.RetentionPolicy;
2222
import java.lang.annotation.Target;
2323

24-
import static org.junit.Assert.*;
2524
import org.junit.Test;
2625

2726
import org.springframework.aop.scope.ScopedProxyUtils;
@@ -35,6 +34,8 @@
3534
import org.springframework.context.annotation.AnnotationConfigUtils;
3635
import org.springframework.context.support.GenericApplicationContext;
3736

37+
import static org.junit.Assert.*;
38+
3839
/**
3940
* Integration tests for handling {@link Qualifier} annotations.
4041
*
@@ -279,14 +280,34 @@ public void testAutowiredFieldResolvesQualifiedCandidate() {
279280
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
280281
context.registerBeanDefinition(JUERGEN, person1);
281282
context.registerBeanDefinition(MARK, person2);
282-
context.registerBeanDefinition("autowired",
283+
context.registerBeanDefinition("autowired",
283284
new RootBeanDefinition(QualifiedFieldTestBean.class));
284285
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
285286
context.refresh();
286287
QualifiedFieldTestBean bean = (QualifiedFieldTestBean) context.getBean("autowired");
287288
assertEquals(JUERGEN, bean.getPerson().getName());
288289
}
289290

291+
@Test
292+
public void testAutowiredFieldResolvesMetaQualifiedCandidate() {
293+
GenericApplicationContext context = new GenericApplicationContext();
294+
ConstructorArgumentValues cavs1 = new ConstructorArgumentValues();
295+
cavs1.addGenericArgumentValue(JUERGEN);
296+
RootBeanDefinition person1 = new RootBeanDefinition(Person.class, cavs1, null);
297+
person1.addQualifier(new AutowireCandidateQualifier(TestQualifier.class));
298+
ConstructorArgumentValues cavs2 = new ConstructorArgumentValues();
299+
cavs2.addGenericArgumentValue(MARK);
300+
RootBeanDefinition person2 = new RootBeanDefinition(Person.class, cavs2, null);
301+
context.registerBeanDefinition(JUERGEN, person1);
302+
context.registerBeanDefinition(MARK, person2);
303+
context.registerBeanDefinition("autowired",
304+
new RootBeanDefinition(MetaQualifiedFieldTestBean.class));
305+
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
306+
context.refresh();
307+
MetaQualifiedFieldTestBean bean = (MetaQualifiedFieldTestBean) context.getBean("autowired");
308+
assertEquals(JUERGEN, bean.getPerson().getName());
309+
}
310+
290311
@Test
291312
public void testAutowiredMethodParameterResolvesQualifiedCandidate() {
292313
GenericApplicationContext context = new GenericApplicationContext();
@@ -596,6 +617,24 @@ public Person getPerson() {
596617
}
597618

598619

620+
private static class MetaQualifiedFieldTestBean {
621+
622+
@MyAutowired
623+
private Person person;
624+
625+
public Person getPerson() {
626+
return this.person;
627+
}
628+
}
629+
630+
631+
@Autowired
632+
@TestQualifier
633+
@Retention(RetentionPolicy.RUNTIME)
634+
public static @interface MyAutowired {
635+
}
636+
637+
599638
private static class QualifiedMethodParameterTestBean {
600639

601640
private Person person;
@@ -706,7 +745,6 @@ public QualifiedPerson(String name) {
706745
}
707746

708747

709-
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
710748
@Retention(RetentionPolicy.RUNTIME)
711749
@Qualifier
712750
public static @interface TestQualifier {
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,117 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.springframework.context.annotation.configuration;
218

3-
import static org.hamcrest.CoreMatchers.equalTo;
4-
import static org.junit.Assert.assertThat;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
521

622
import org.junit.Test;
23+
import test.beans.TestBean;
24+
725
import org.springframework.beans.factory.annotation.Autowired;
826
import org.springframework.beans.factory.annotation.Qualifier;
927
import org.springframework.context.ApplicationContext;
1028
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
1129
import org.springframework.context.annotation.Bean;
1230
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.context.annotation.Lazy;
32+
import org.springframework.context.annotation.Scope;
1333
import org.springframework.stereotype.Component;
1434

15-
import test.beans.TestBean;
35+
import static org.hamcrest.CoreMatchers.*;
36+
import static org.junit.Assert.*;
1637

1738
/**
1839
* Tests proving that @Qualifier annotations work when used
1940
* with @Configuration classes on @Bean methods.
2041
*
2142
* @author Chris Beams
43+
* @author Juergen Hoeller
2244
*/
2345
public class BeanMethodQualificationTests {
2446

2547
@Test
26-
public void test() {
27-
ApplicationContext ctx =
28-
new AnnotationConfigApplicationContext(Config.class, Pojo.class);
29-
Pojo pojo = ctx.getBean(Pojo.class);
48+
public void testStandard() {
49+
AnnotationConfigApplicationContext ctx =
50+
new AnnotationConfigApplicationContext(StandardConfig.class, StandardPojo.class);
51+
assertFalse(ctx.getBeanFactory().containsSingleton("testBean1"));
52+
StandardPojo pojo = ctx.getBean(StandardPojo.class);
53+
assertThat(pojo.testBean.getName(), equalTo("interesting"));
54+
}
55+
56+
@Test
57+
public void testCustom() {
58+
AnnotationConfigApplicationContext ctx =
59+
new AnnotationConfigApplicationContext(CustomConfig.class, CustomPojo.class);
60+
assertFalse(ctx.getBeanFactory().containsSingleton("testBean1"));
61+
CustomPojo pojo = ctx.getBean(CustomPojo.class);
3062
assertThat(pojo.testBean.getName(), equalTo("interesting"));
3163
}
32-
64+
65+
3366
@Configuration
34-
static class Config {
35-
@Bean
36-
@Qualifier("interesting")
67+
static class StandardConfig {
68+
@Bean @Lazy @Qualifier("interesting")
3769
public TestBean testBean1() {
3870
return new TestBean("interesting");
3971
}
4072

41-
@Bean
42-
@Qualifier("boring")
73+
@Bean @Qualifier("boring")
4374
public TestBean testBean2() {
4475
return new TestBean("boring");
4576
}
4677
}
47-
48-
@Component
49-
static class Pojo {
78+
79+
@Component @Lazy
80+
static class StandardPojo {
5081
@Autowired @Qualifier("interesting") TestBean testBean;
5182
}
83+
84+
@Configuration
85+
static class CustomConfig {
86+
@InterestingBean
87+
public TestBean testBean1() {
88+
return new TestBean("interesting");
89+
}
90+
91+
@Bean @Qualifier("boring")
92+
public TestBean testBean2() {
93+
return new TestBean("boring");
94+
}
95+
}
96+
97+
@InterestingPojo
98+
static class CustomPojo {
99+
@InterestingNeed TestBean testBean;
100+
}
101+
102+
@Bean @Lazy @Qualifier("interesting")
103+
@Retention(RetentionPolicy.RUNTIME)
104+
public @interface InterestingBean {
105+
}
106+
107+
@Autowired @Qualifier("interesting")
108+
@Retention(RetentionPolicy.RUNTIME)
109+
public @interface InterestingNeed {
110+
}
111+
112+
@Component @Lazy
113+
@Retention(RetentionPolicy.RUNTIME)
114+
public @interface InterestingPojo {
115+
}
116+
52117
}

0 commit comments

Comments
 (0)