Skip to content

Commit 1d30bf8

Browse files
committed
Favor 'local' annotations over inherited ones
Prior to this commit, the implementations of findAnnotation() in AnnotationUtils and getAnnotationAttributes() in AnnotatedElementUtils favored inherited annotations and inherited composed annotations over composed annotations that are declared closer to the starting class passed to these methods. This commit addresses this issue as follows: - Refactored AnnotationUtils to use getDeclaredAnnotation() and getDeclaredAnnotations() instead of getAnnotation() and getAnnotations() where appropriate. - AnnotatedElementUtils.doProcess() supports a traverseClassHierarchy flag to control whether the class hierarchy should be traversed, using getDeclaredAnnotations() instead of getAnnotations() if the flag is true. - Overhauled Javadoc in AnnotatedElementUtils. Issue: SPR-11475
1 parent 0616cbc commit 1d30bf8

File tree

4 files changed

+217
-79
lines changed

4 files changed

+217
-79
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

Lines changed: 111 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.springframework.util.LinkedMultiValueMap;
2727
import org.springframework.util.MultiValueMap;
2828

29+
import static org.springframework.core.annotation.AnnotationUtils.*;
30+
2931
/**
3032
* Utility class used to collect all annotation values including those declared on
3133
* meta-annotations.
@@ -39,14 +41,16 @@ public class AnnotatedElementUtils {
3941

4042
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
4143
final Set<String> types = new LinkedHashSet<String>();
42-
process(element, annotationType, new Processor<Object>() {
44+
process(element, annotationType, true, new Processor<Object>() {
45+
4346
@Override
44-
public Object process(Annotation annotation, int depth) {
45-
if (depth > 0) {
47+
public Object process(Annotation annotation, int metaDepth) {
48+
if (metaDepth > 0) {
4649
types.add(annotation.annotationType().getName());
4750
}
4851
return null;
4952
}
53+
5054
@Override
5155
public void postProcess(Annotation annotation, Object result) {
5256
}
@@ -55,26 +59,30 @@ public void postProcess(Annotation annotation, Object result) {
5559
}
5660

5761
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
58-
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
62+
return Boolean.TRUE.equals(process(element, annotationType, true, new Processor<Boolean>() {
63+
5964
@Override
60-
public Boolean process(Annotation annotation, int depth) {
61-
if (depth > 0) {
62-
return true;
65+
public Boolean process(Annotation annotation, int metaDepth) {
66+
if (metaDepth > 0) {
67+
return Boolean.TRUE;
6368
}
6469
return null;
6570
}
71+
6672
@Override
6773
public void postProcess(Annotation annotation, Boolean result) {
6874
}
6975
}));
7076
}
7177

7278
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
73-
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
79+
return Boolean.TRUE.equals(process(element, annotationType, true, new Processor<Boolean>() {
80+
7481
@Override
75-
public Boolean process(Annotation annotation, int depth) {
76-
return true;
82+
public Boolean process(Annotation annotation, int metaDepth) {
83+
return Boolean.TRUE;
7784
}
85+
7886
@Override
7987
public void postProcess(Annotation annotation, Boolean result) {
8088
}
@@ -85,20 +93,21 @@ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement elem
8593
return getAnnotationAttributes(element, annotationType, false, false);
8694
}
8795

88-
public static AnnotationAttributes getAnnotationAttributes(
89-
AnnotatedElement element, String annotationType,
96+
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
9097
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
9198

92-
return process(element, annotationType, new Processor<AnnotationAttributes>() {
99+
return process(element, annotationType, true, new Processor<AnnotationAttributes>() {
100+
93101
@Override
94-
public AnnotationAttributes process(Annotation annotation, int depth) {
102+
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
95103
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
96104
}
105+
97106
@Override
98107
public void postProcess(Annotation annotation, AnnotationAttributes result) {
99108
for (String key : result.keySet()) {
100-
if (!"value".equals(key)) {
101-
Object value = AnnotationUtils.getValue(annotation, key);
109+
if (!VALUE.equals(key)) {
110+
Object value = getValue(annotation, key);
102111
if (value != null) {
103112
result.put(key, value);
104113
}
@@ -108,28 +117,33 @@ public void postProcess(Annotation annotation, AnnotationAttributes result) {
108117
});
109118
}
110119

111-
public static MultiValueMap<String, Object> getAllAnnotationAttributes(
112-
AnnotatedElement element, final String annotationType,
113-
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
120+
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
121+
final String annotationType) {
122+
return getAllAnnotationAttributes(element, annotationType, false, false);
123+
}
124+
125+
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
126+
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
114127

115128
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
116-
process(element, annotationType, new Processor<Void>() {
129+
process(element, annotationType, false, new Processor<Void>() {
130+
117131
@Override
118-
public Void process(Annotation annotation, int depth) {
132+
public Void process(Annotation annotation, int metaDepth) {
119133
if (annotation.annotationType().getName().equals(annotationType)) {
120-
for (Map.Entry<String, Object> entry :
121-
AnnotationUtils.getAnnotationAttributes(
122-
annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
134+
for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes(annotation,
135+
classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
123136
attributes.add(entry.getKey(), entry.getValue());
124137
}
125138
}
126139
return null;
127140
}
141+
128142
@Override
129143
public void postProcess(Annotation annotation, Void result) {
130144
for (String key : attributes.keySet()) {
131-
if (!"value".equals(key)) {
132-
Object value = AnnotationUtils.getValue(annotation, key);
145+
if (!VALUE.equals(key)) {
146+
Object value = getValue(annotation, key);
133147
if (value != null) {
134148
attributes.add(key, value);
135149
}
@@ -141,45 +155,91 @@ public void postProcess(Annotation annotation, Void result) {
141155
}
142156

143157
/**
144-
* Process all annotations of the specified annotation type and recursively all
145-
* meta-annotations on the specified type.
158+
* Process all annotations of the specified {@code annotationType} and
159+
* recursively all meta-annotations on the specified {@code element}.
160+
*
161+
* <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought
162+
* annotation is neither <em>directly present</em> on the given element nor
163+
* present on the given element as a meta-annotation, then the algorithm will
164+
* recursively search through the class hierarchy of the given element.
165+
*
146166
* @param element the annotated element
147-
* @param annotationType the annotation type to find. Only items of the specified
148-
* type or meta-annotations of the specified type will be processed.
149-
* @param processor the processor
167+
* @param annotationType the annotation type to find
168+
* @param traverseClassHierarchy whether or not to traverse up the class
169+
* hierarchy recursively
170+
* @param processor the processor to delegate to
150171
* @return the result of the processor
151172
*/
152-
private static <T> T process(AnnotatedElement element, String annotationType, Processor<T> processor) {
153-
return doProcess(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
173+
private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
174+
Processor<T> processor) {
175+
return doProcess(element, annotationType, traverseClassHierarchy, processor, new HashSet<AnnotatedElement>(), 0);
154176
}
155177

156-
private static <T> T doProcess(AnnotatedElement element, String annotationType,
157-
Processor<T> processor, Set<AnnotatedElement> visited, int depth) {
178+
/**
179+
* Perform the search algorithm for the {@link #process} method, avoiding
180+
* endless recursion by tracking which annotated elements have already been
181+
* <em>visited</em>.
182+
*
183+
* <p>The {@code metaDepth} parameter represents the depth of the annotation
184+
* relative to the initial element. For example, an annotation that is
185+
* <em>present</em> on the element will have a depth of 0; a meta-annotation
186+
* will have a depth of 1; and a meta-meta-annotation will have a depth of 2.
187+
*
188+
* @param element the annotated element
189+
* @param annotationType the annotation type to find
190+
* @param traverseClassHierarchy whether or not to traverse up the class
191+
* hierarchy recursively
192+
* @param processor the processor to delegate to
193+
* @param visited the set of annotated elements that have already been visited
194+
* @param metaDepth the depth of the annotation relative to the initial element
195+
* @return the result of the processor
196+
*/
197+
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
198+
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
158199

159200
if (visited.add(element)) {
160-
for (Annotation annotation : element.getAnnotations()) {
161-
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
162-
T result = processor.process(annotation, depth);
201+
202+
Annotation[] annotations = traverseClassHierarchy ? element.getDeclaredAnnotations()
203+
: element.getAnnotations();
204+
205+
for (Annotation annotation : annotations) {
206+
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
207+
T result = processor.process(annotation, metaDepth);
163208
if (result != null) {
164209
return result;
165210
}
166-
result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth + 1);
211+
result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, processor,
212+
visited, metaDepth + 1);
167213
if (result != null) {
168214
processor.postProcess(annotation, result);
169215
return result;
170216
}
171217
}
172218
}
173-
for (Annotation annotation : element.getAnnotations()) {
174-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
175-
T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth);
219+
220+
for (Annotation annotation : annotations) {
221+
if (!isInJavaLangAnnotationPackage(annotation)) {
222+
T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
223+
processor, visited, metaDepth);
176224
if (result != null) {
177225
processor.postProcess(annotation, result);
178226
return result;
179227
}
180228
}
181229
}
230+
231+
if (traverseClassHierarchy && element instanceof Class) {
232+
Class<?> superclass = ((Class<?>) element).getSuperclass();
233+
if (superclass != null && !superclass.equals(Object.class)) {
234+
T result = doProcess(superclass, annotationType, traverseClassHierarchy, processor, visited,
235+
metaDepth);
236+
if (result != null) {
237+
return result;
238+
}
239+
}
240+
}
182241
}
242+
183243
return null;
184244
}
185245

@@ -192,13 +252,18 @@ private static interface Processor<T> {
192252

193253
/**
194254
* Called to process the annotation.
255+
*
256+
* <p>The {@code metaDepth} parameter represents the depth of the
257+
* annotation relative to the initial element. For example, an annotation
258+
* that is <em>present</em> on the element will have a depth of 0; a
259+
* meta-annotation will have a depth of 1; and a meta-meta-annotation
260+
* will have a depth of 2.
261+
*
195262
* @param annotation the annotation to process
196-
* @param depth the depth of the annotation relative to the initial match.
197-
* For example, a matched annotation will have a depth of 0, a meta-annotation
198-
* 1 and a meta-meta-annotation 2
263+
* @param metaDepth the depth of the annotation relative to the initial element
199264
* @return the result of the processing or {@code null} to continue
200265
*/
201-
T process(Annotation annotation, int depth);
266+
T process(Annotation annotation, int metaDepth);
202267

203268
void postProcess(Annotation annotation, T result);
204269
}

spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A>
278278
Set<Annotation> visited) {
279279
Assert.notNull(clazz, "Class must not be null");
280280

281-
A annotation = clazz.getAnnotation(annotationType);
281+
A annotation = clazz.getDeclaredAnnotation(annotationType);
282282
if (annotation != null) {
283283
return annotation;
284284
}
@@ -288,7 +288,7 @@ private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A>
288288
return annotation;
289289
}
290290
}
291-
for (Annotation ann : clazz.getAnnotations()) {
291+
for (Annotation ann : clazz.getDeclaredAnnotations()) {
292292
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
293293
annotation = findAnnotation(ann.annotationType(), annotationType, visited);
294294
if (annotation != null) {

0 commit comments

Comments
 (0)