Skip to content

Commit 04d6afe

Browse files
committed
Support arbitrary levels of meta-annotations in TypeDescriptor
Prior to this commit, the `getAnnotation()` method in `TypeDescriptor` only supported a single level of meta-annotations. In other words, the annotation hierarchy would not be exhaustively searched. This commit provides support for arbitrary levels of meta-annotations in `TypeDescriptor` by delegating to `AnnotationUtils.findAnnotation()` within `TypeDescriptor.getAnnotation()`. Issue: SPR-12793
1 parent 5f03c97 commit 04d6afe

File tree

2 files changed

+58
-7
lines changed

2 files changed

+58
-7
lines changed

spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.core.MethodParameter;
2929
import org.springframework.core.ResolvableType;
30+
import org.springframework.core.annotation.AnnotationUtils;
3031
import org.springframework.lang.UsesJava8;
3132
import org.springframework.util.Assert;
3233
import org.springframework.util.ClassUtils;
@@ -237,6 +238,8 @@ public Annotation[] getAnnotations() {
237238

238239
/**
239240
* Determine if this type descriptor has the specified annotation.
241+
* <p>As of Spring Framework 4.2, this method supports arbitrary levels
242+
* of meta-annotations.
240243
* @param annotationType the annotation type
241244
* @return <tt>true</tt> if the annotation is present
242245
*/
@@ -245,19 +248,27 @@ public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
245248
}
246249

247250
/**
248-
* Obtain the annotation associated with this type descriptor of the specified type.
251+
* Obtain the annotation of the specified {@code annotationType} that is
252+
* on this type descriptor.
253+
* <p>As of Spring Framework 4.2, this method supports arbitrary levels
254+
* of meta-annotations.
249255
* @param annotationType the annotation type
250256
* @return the annotation, or {@code null} if no such annotation exists on this type descriptor
251257
*/
252258
@SuppressWarnings("unchecked")
253259
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
260+
// Search in annotations that are "present" (i.e., locally declared or inherited)
261+
//
262+
// NOTE: this unfortunately favors inherited annotations over locally declared composed annotations.
254263
for (Annotation annotation : getAnnotations()) {
255264
if (annotation.annotationType().equals(annotationType)) {
256265
return (T) annotation;
257266
}
258267
}
259-
for (Annotation metaAnn : getAnnotations()) {
260-
T ann = metaAnn.annotationType().getAnnotation(annotationType);
268+
269+
// Search in annotation hierarchy
270+
for (Annotation composedAnnotation : getAnnotations()) {
271+
T ann = AnnotationUtils.findAnnotation(composedAnnotation.annotationType(), annotationType);
261272
if (ann != null) {
262273
return ann;
263274
}

spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -20,6 +20,7 @@
2020
import java.io.ByteArrayOutputStream;
2121
import java.io.ObjectInputStream;
2222
import java.io.ObjectOutputStream;
23+
import java.lang.annotation.Annotation;
2324
import java.lang.annotation.ElementType;
2425
import java.lang.annotation.Retention;
2526
import java.lang.annotation.RetentionPolicy;
@@ -50,6 +51,7 @@
5051
* @author Keith Donald
5152
* @author Andy Clement
5253
* @author Phillip Webb
54+
* @author Sam Brannen
5355
*/
5456
@SuppressWarnings("rawtypes")
5557
public class TypeDescriptorTests {
@@ -369,22 +371,60 @@ public void setProperty(Map<List<Integer>, List<Long>> property) {
369371
@MethodAnnotation3
370372
private Map<List<Integer>, List<Long>> property;
371373

372-
@Target({ElementType.METHOD})
374+
375+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
373376
@Retention(RetentionPolicy.RUNTIME)
374377
public @interface MethodAnnotation1 {
375-
376378
}
377379

378380
@Target({ElementType.METHOD})
379381
@Retention(RetentionPolicy.RUNTIME)
380382
public @interface MethodAnnotation2 {
381-
382383
}
383384

384385
@Target({ElementType.FIELD})
385386
@Retention(RetentionPolicy.RUNTIME)
386387
public @interface MethodAnnotation3 {
388+
}
389+
390+
@MethodAnnotation1
391+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
392+
@Retention(RetentionPolicy.RUNTIME)
393+
public @interface ComposedMethodAnnotation1 {}
394+
395+
@ComposedMethodAnnotation1
396+
@Target(ElementType.METHOD)
397+
@Retention(RetentionPolicy.RUNTIME)
398+
public @interface ComposedComposedMethodAnnotation1 {}
399+
400+
@MethodAnnotation1
401+
public void methodWithLocalAnnotation() {}
387402

403+
@ComposedMethodAnnotation1
404+
public void methodWithComposedAnnotation() {}
405+
406+
@ComposedComposedMethodAnnotation1
407+
public void methodWithComposedComposedAnnotation() {}
408+
409+
private void assertAnnotationFoundOnMethod(Class<? extends Annotation> annotationType, String methodName) throws Exception {
410+
TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(getClass().getMethod(methodName), -1));
411+
assertNotNull("Should have found @" + annotationType.getSimpleName() + " on " + methodName + ".",
412+
typeDescriptor.getAnnotation(annotationType));
413+
}
414+
415+
@Test
416+
public void getAnnotationOnMethodThatIsLocallyAnnotated() throws Exception {
417+
assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithLocalAnnotation");
418+
}
419+
420+
@Test
421+
public void getAnnotationOnMethodThatIsMetaAnnotated() throws Exception {
422+
assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithComposedAnnotation");
423+
}
424+
425+
@Test
426+
public void getAnnotationOnMethodThatIsMetaMetaAnnotated() throws Exception {
427+
assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithComposedComposedAnnotation");
388428
}
389429

390430
@Test

0 commit comments

Comments
 (0)