Skip to content

Commit 6d36498

Browse files
committed
Allow meta-annotations to override attributes from their parent
Issue: SPR-10181
1 parent 87c2d6f commit 6d36498

File tree

11 files changed

+434
-261
lines changed

11 files changed

+434
-261
lines changed

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

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 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.
@@ -55,7 +55,8 @@
5555
import org.springframework.core.MethodParameter;
5656
import org.springframework.core.Ordered;
5757
import org.springframework.core.PriorityOrdered;
58-
import org.springframework.core.annotation.AnnotationUtils;
58+
import org.springframework.core.annotation.AnnotatedElementUtils;
59+
import org.springframework.core.annotation.AnnotationAttributes;
5960
import org.springframework.util.Assert;
6061
import org.springframework.util.ClassUtils;
6162
import org.springframework.util.ReflectionUtils;
@@ -235,7 +236,7 @@ public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, Strin
235236
Constructor<?> requiredConstructor = null;
236237
Constructor<?> defaultConstructor = null;
237238
for (Constructor<?> candidate : rawCandidates) {
238-
Annotation annotation = findAutowiredAnnotation(candidate);
239+
AnnotationAttributes annotation = findAutowiredAnnotation(candidate);
239240
if (annotation != null) {
240241
if (requiredConstructor != null) {
241242
throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate +
@@ -333,7 +334,7 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
333334
do {
334335
LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<InjectionMetadata.InjectedElement>();
335336
for (Field field : targetClass.getDeclaredFields()) {
336-
Annotation annotation = findAutowiredAnnotation(field);
337+
AnnotationAttributes annotation = findAutowiredAnnotation(field);
337338
if (annotation != null) {
338339
if (Modifier.isStatic(field.getModifiers())) {
339340
if (logger.isWarnEnabled()) {
@@ -347,7 +348,7 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
347348
}
348349
for (Method method : targetClass.getDeclaredMethods()) {
349350
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
350-
Annotation annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
351+
AnnotationAttributes annotation = BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod) ?
351352
findAutowiredAnnotation(bridgedMethod) : findAutowiredAnnotation(method);
352353
if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
353354
if (Modifier.isStatic(method.getModifiers())) {
@@ -374,16 +375,29 @@ private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
374375
return new InjectionMetadata(clazz, elements);
375376
}
376377

377-
private Annotation findAutowiredAnnotation(AccessibleObject ao) {
378+
private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
378379
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
379-
Annotation annotation = AnnotationUtils.getAnnotation(ao, type);
380+
AnnotationAttributes annotation = AnnotatedElementUtils.getAnnotationAttributes(ao, type.getName());
380381
if (annotation != null) {
381382
return annotation;
382383
}
383384
}
384385
return null;
385386
}
386387

388+
/**
389+
* Determine if the annotated field or method requires its dependency.
390+
* <p>A 'required' dependency means that autowiring should fail when no beans
391+
* are found. Otherwise, the autowiring process will simply bypass the field
392+
* or method when no beans are found.
393+
* @param annotation the Autowired annotation
394+
* @return whether the annotation indicates that a dependency is required
395+
*/
396+
protected boolean determineRequiredStatus(AnnotationAttributes annotation) {
397+
return (!annotation.containsKey(this.requiredParameterName) ||
398+
this.requiredParameterValue == annotation.getBoolean(this.requiredParameterName));
399+
}
400+
387401
/**
388402
* Obtain all beans of the given type as autowire candidates.
389403
* @param type the type of the bean
@@ -398,31 +412,6 @@ protected <T> Map<String, T> findAutowireCandidates(Class<T> type) throws BeansE
398412
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type);
399413
}
400414

401-
/**
402-
* Determine if the annotated field or method requires its dependency.
403-
* <p>A 'required' dependency means that autowiring should fail when no beans
404-
* are found. Otherwise, the autowiring process will simply bypass the field
405-
* or method when no beans are found.
406-
* @param annotation the Autowired annotation
407-
* @return whether the annotation indicates that a dependency is required
408-
*/
409-
protected boolean determineRequiredStatus(Annotation annotation) {
410-
try {
411-
Method method = ReflectionUtils.findMethod(annotation.annotationType(), this.requiredParameterName);
412-
if (method == null) {
413-
// annotations like @Inject and @Value don't have a method (attribute) named "required"
414-
// -> default to required status
415-
return true;
416-
}
417-
return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, annotation));
418-
}
419-
catch (Exception ex) {
420-
// an exception was thrown during reflective invocation of the required attribute
421-
// -> default to required status
422-
return true;
423-
}
424-
}
425-
426415
/**
427416
* Register the specified bean as dependent on the autowired beans.
428417
*/

spring-context/src/test/java/org/springframework/context/annotation/AnnotationScopeMetadataResolverTests.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.lang.annotation.RetentionPolicy;
20-
import java.lang.annotation.Retention;
2119
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
2222
import java.lang.annotation.Target;
2323

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

2827
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
2928
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
3029
import org.springframework.beans.factory.config.BeanDefinition;
3130

31+
import static org.junit.Assert.*;
32+
3233
/**
3334
* @author Rick Evans
3435
* @author Chris Beams
@@ -83,6 +84,15 @@ public void testCustomRequestScope() {
8384
assertEquals(ScopedProxyMode.NO, scopeMetadata.getScopedProxyMode());
8485
}
8586

87+
@Test
88+
public void testCustomRequestScopeWithAttribute() {
89+
AnnotatedBeanDefinition bd = new AnnotatedGenericBeanDefinition(AnnotatedWithCustomRequestScopeWithAttribute.class);
90+
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(bd);
91+
assertNotNull("resolveScopeMetadata(..) must *never* return null.", scopeMetadata);
92+
assertEquals("request", scopeMetadata.getScopeName());
93+
assertEquals(ScopedProxyMode.TARGET_CLASS, scopeMetadata.getScopedProxyMode());
94+
}
95+
8696
@Test(expected=IllegalArgumentException.class)
8797
public void testCtorWithNullScopedProxyMode() {
8898
new AnnotationScopeMetadataResolver(null);
@@ -98,17 +108,21 @@ public void testSetScopeAnnotationTypeWithNullType() {
98108
private static final class AnnotatedWithSingletonScope {
99109
}
100110

101-
102111
@Scope("prototype")
103112
private static final class AnnotatedWithPrototypeScope {
104113
}
105114

106-
107115
@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS)
108116
private static final class AnnotatedWithScopedProxy {
109117
}
110118

111119

120+
@Target({ElementType.TYPE, ElementType.METHOD})
121+
@Retention(RetentionPolicy.RUNTIME)
122+
@Scope("request")
123+
public @interface CustomRequestScope {
124+
}
125+
112126
@CustomRequestScope
113127
private static final class AnnotatedWithCustomRequestScope {
114128
}
@@ -117,8 +131,13 @@ private static final class AnnotatedWithCustomRequestScope {
117131
@Target({ElementType.TYPE, ElementType.METHOD})
118132
@Retention(RetentionPolicy.RUNTIME)
119133
@Scope("request")
120-
public @interface CustomRequestScope {
134+
public @interface CustomRequestScopeWithAttribute {
135+
136+
ScopedProxyMode proxyMode();
137+
}
121138

139+
@CustomRequestScopeWithAttribute(proxyMode = ScopedProxyMode.TARGET_CLASS)
140+
private static final class AnnotatedWithCustomRequestScopeWithAttribute {
122141
}
123142

124143
}

spring-context/src/test/java/org/springframework/context/annotation/configuration/BeanMethodQualificationTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.lang.annotation.RetentionPolicy;
2121

2222
import org.junit.Test;
23+
24+
import org.springframework.tests.sample.beans.NestedTestBean;
2325
import org.springframework.tests.sample.beans.TestBean;
2426

2527
import org.springframework.beans.factory.annotation.Autowired;
@@ -60,9 +62,19 @@ public void testCustom() {
6062
assertThat(pojo.testBean.getName(), equalTo("interesting"));
6163
}
6264

65+
@Test
66+
public void testCustomWithAttributeOverride() {
67+
AnnotationConfigApplicationContext ctx =
68+
new AnnotationConfigApplicationContext(CustomConfigWithAttributeOverride.class, CustomPojo.class);
69+
assertFalse(ctx.getBeanFactory().containsSingleton("testBeanX"));
70+
CustomPojo pojo = ctx.getBean(CustomPojo.class);
71+
assertThat(pojo.testBean.getName(), equalTo("interesting"));
72+
}
73+
6374

6475
@Configuration
6576
static class StandardConfig {
77+
6678
@Bean @Lazy @Qualifier("interesting")
6779
public TestBean testBean1() {
6880
return new TestBean("interesting");
@@ -76,11 +88,13 @@ public TestBean testBean2() {
7688

7789
@Component @Lazy
7890
static class StandardPojo {
91+
7992
@Autowired @Qualifier("interesting") TestBean testBean;
8093
}
8194

8295
@Configuration
8396
static class CustomConfig {
97+
8498
@InterestingBean
8599
public TestBean testBean1() {
86100
return new TestBean("interesting");
@@ -92,21 +106,52 @@ public TestBean testBean2() {
92106
}
93107
}
94108

109+
@Configuration
110+
static class CustomConfigWithAttributeOverride {
111+
112+
@InterestingBeanWithName(name="testBeanX")
113+
public TestBean testBean1() {
114+
return new TestBean("interesting");
115+
}
116+
117+
@Bean @Qualifier("boring")
118+
public TestBean testBean2() {
119+
return new TestBean("boring");
120+
}
121+
}
122+
95123
@InterestingPojo
96124
static class CustomPojo {
125+
97126
@InterestingNeed TestBean testBean;
127+
128+
@InterestingNeedWithRequiredOverride(required=false) NestedTestBean nestedTestBean;
98129
}
99130

100131
@Bean @Lazy @Qualifier("interesting")
101132
@Retention(RetentionPolicy.RUNTIME)
102133
public @interface InterestingBean {
103134
}
104135

136+
@Bean @Lazy @Qualifier("interesting")
137+
@Retention(RetentionPolicy.RUNTIME)
138+
public @interface InterestingBeanWithName {
139+
140+
String name();
141+
}
142+
105143
@Autowired @Qualifier("interesting")
106144
@Retention(RetentionPolicy.RUNTIME)
107145
public @interface InterestingNeed {
108146
}
109147

148+
@Autowired @Qualifier("interesting")
149+
@Retention(RetentionPolicy.RUNTIME)
150+
public @interface InterestingNeedWithRequiredOverride {
151+
152+
boolean required();
153+
}
154+
110155
@Component @Lazy
111156
@Retention(RetentionPolicy.RUNTIME)
112157
public @interface InterestingPojo {

0 commit comments

Comments
 (0)