Skip to content

Commit 7f0f04d

Browse files
committed
Support annotations on interfaces in AnnotatedElementUtils
This commit introduces support in AnnotatedElementUtils for finding annotations declared on interfaces at the type level. NB: this commit does not include support for finding annotations declared on interface methods. In order to maintain backward compatibility with @transactional annotation attribute processing, a new getAnnotationAttributes() method has been added to AnnotatedElementUtils that provides a flag to control whether interfaces should be searched. SpringTransactionAnnotationParser and JtaTransactionAnnotationParser have been updated accordingly to ensure that interfaces are not unintentionally searched in the @transactional resolution process. This commit also introduces additional tests and updates TODOs for SPR-12738. Issue: SPR-12944, SPR-12738
1 parent 9b7fd8b commit 7f0f04d

File tree

5 files changed

+208
-45
lines changed

5 files changed

+208
-45
lines changed

spring-aspects/src/test/java/org/springframework/transaction/aspectj/TransactionAspectTests.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 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.
@@ -18,7 +18,6 @@
1818

1919
import java.lang.reflect.Method;
2020

21-
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
2221
import org.springframework.tests.transaction.CallCountingTransactionManager;
2322
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
2423
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@@ -28,8 +27,10 @@
2827
* @author Rod Johnson
2928
* @author Ramnivas Laddad
3029
* @author Juergen Hoeller
30+
* @author Sam Brannen
3131
*/
32-
public class TransactionAspectTests extends AbstractDependencyInjectionSpringContextTests {
32+
@SuppressWarnings("deprecation")
33+
public class TransactionAspectTests extends org.springframework.test.AbstractDependencyInjectionSpringContextTests {
3334

3435
private TransactionAspectSupport transactionAspect;
3536

@@ -206,17 +207,14 @@ public Object performTransactionalOperation() throws Throwable {
206207
* Note: resolution does not occur. Thus we can't make a class transactional if
207208
* it implements a transactionally annotated interface. This behaviour could only
208209
* be changed in AbstractFallbackTransactionAttributeSource in Spring proper.
209-
* @throws SecurityException
210-
* @throws NoSuchMethodException
211210
*/
212-
public void testDoesNotResolveTxAnnotationOnMethodFromClassImplementingAnnotatedInterface() throws SecurityException, NoSuchMethodException {
211+
public void testDoesNotResolveTxAnnotationOnMethodFromClassImplementingAnnotatedInterface() throws Exception {
213212
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
214213
Method m = ImplementsAnnotatedInterface.class.getMethod("echo", Throwable.class);
215214
TransactionAttribute ta = atas.getTransactionAttribute(m, ImplementsAnnotatedInterface.class);
216215
assertNull(ta);
217216
}
218217

219-
220218
public void testDefaultRollbackOnImplementationOfAnnotatedInterface() throws Throwable {
221219
// testRollback(new TransactionOperationCallback() {
222220
// public Object performTransactionalOperation() throws Throwable {

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

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import org.springframework.util.MultiValueMap;
2828

2929
/**
30-
* Utility class used to collect all annotation values including those declared on
31-
* meta-annotations.
30+
* Utility class used to collect all annotation attributes, including those
31+
* declared on meta-annotations.
3232
*
3333
* @author Phillip Webb
3434
* @author Juergen Hoeller
@@ -39,7 +39,7 @@ public class AnnotatedElementUtils {
3939

4040
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
4141
final Set<String> types = new LinkedHashSet<String>();
42-
process(element, annotationType, false, new Processor<Object>() {
42+
process(element, annotationType, true, false, new Processor<Object>() {
4343
@Override
4444
public Object process(Annotation annotation, int metaDepth) {
4545
if (metaDepth > 0) {
@@ -55,7 +55,7 @@ public void postProcess(Annotation annotation, Object result) {
5555
}
5656

5757
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
58-
return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
58+
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
5959
@Override
6060
public Boolean process(Annotation annotation, int metaDepth) {
6161
if (metaDepth > 0) {
@@ -70,7 +70,7 @@ public void postProcess(Annotation annotation, Boolean result) {
7070
}
7171

7272
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
73-
return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
73+
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
7474
@Override
7575
public Boolean process(Annotation annotation, int metaDepth) {
7676
return Boolean.TRUE;
@@ -81,14 +81,59 @@ public void postProcess(Annotation annotation, Boolean result) {
8181
}));
8282
}
8383

84+
/**
85+
* Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
86+
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
87+
*
88+
* @param element the annotated element
89+
* @param annotationType the annotation type to find
90+
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
91+
*/
8492
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
8593
return getAnnotationAttributes(element, annotationType, false, false);
8694
}
8795

96+
/**
97+
* Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean)},
98+
* supplying {@code true} for {@code searchInterfaces} and {@code false} for {@code searchClassHierarchy}.
99+
*
100+
* @param element the annotated element
101+
* @param annotationType the annotation type to find
102+
* @param classValuesAsString whether to convert Class references into
103+
* Strings or to preserve them as Class references
104+
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances
105+
* into {@link AnnotationAttributes} maps or to preserve them as Annotation
106+
* instances
107+
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean)
108+
*/
88109
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
89-
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
110+
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
111+
return getAnnotationAttributes(element, annotationType, true, false, classValuesAsString,
112+
nestedAnnotationsAsMap);
113+
}
90114

91-
return process(element, annotationType, false, new Processor<AnnotationAttributes>() {
115+
/**
116+
* Find annotation attributes of the specified {@code annotationType} in
117+
* the annotation hierarchy of the supplied {@link AnnotatedElement},
118+
* and merge the results into an {@link AnnotationAttributes} map.
119+
*
120+
* @param element the annotated element
121+
* @param annotationType the annotation type to find
122+
* @param searchInterfaces whether or not to search on interfaces, if the
123+
* annotated element is a class
124+
* @param searchClassHierarchy whether or not to search the class hierarchy
125+
* recursively, if the annotated element is a class
126+
* @param classValuesAsString whether to convert Class references into
127+
* Strings or to preserve them as Class references
128+
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances
129+
* into {@link AnnotationAttributes} maps or to preserve them as Annotation
130+
* instances
131+
*/
132+
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
133+
boolean searchInterfaces, boolean searchClassHierarchy, final boolean classValuesAsString,
134+
final boolean nestedAnnotationsAsMap) {
135+
136+
return process(element, annotationType, searchInterfaces, searchClassHierarchy, new Processor<AnnotationAttributes>() {
92137
@Override
93138
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
94139
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
@@ -115,7 +160,7 @@ public static MultiValueMap<String, Object> getAllAnnotationAttributes(Annotated
115160
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
116161

117162
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
118-
process(element, annotationType, false, new Processor<Void>() {
163+
process(element, annotationType, true, false, new Processor<Void>() {
119164
@Override
120165
public Void process(Annotation annotation, int metaDepth) {
121166
if (annotation.annotationType().getName().equals(annotationType)) {
@@ -144,22 +189,26 @@ public void postProcess(Annotation annotation, Void result) {
144189
/**
145190
* Process all annotations of the specified {@code annotationType} and
146191
* recursively all meta-annotations on the specified {@code element}.
147-
* <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought
192+
*
193+
* <p>If the {@code searchClassHierarchy} flag is {@code true} and the sought
148194
* annotation is neither <em>directly present</em> on the given element nor
149195
* present on the given element as a meta-annotation, then the algorithm will
150196
* recursively search through the class hierarchy of the given element.
197+
*
151198
* @param element the annotated element
152199
* @param annotationType the annotation type to find
153-
* @param traverseClassHierarchy whether or not to traverse up the class
154-
* hierarchy recursively
200+
* @param searchInterfaces whether or not to search on interfaces, if the
201+
* annotated element is a class
202+
* @param searchClassHierarchy whether or not to search the class hierarchy
203+
* recursively, if the annotated element is a class
155204
* @param processor the processor to delegate to
156205
* @return the result of the processor
157206
*/
158-
private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
159-
Processor<T> processor) {
207+
private static <T> T process(AnnotatedElement element, String annotationType, boolean searchInterfaces,
208+
boolean searchClassHierarchy, Processor<T> processor) {
160209

161210
try {
162-
return doProcess(element, annotationType, traverseClassHierarchy, processor,
211+
return doProcess(element, annotationType, searchInterfaces, searchClassHierarchy, processor,
163212
new HashSet<AnnotatedElement>(), 0);
164213
}
165214
catch (Throwable ex) {
@@ -171,54 +220,79 @@ private static <T> T process(AnnotatedElement element, String annotationType, bo
171220
* Perform the search algorithm for the {@link #process} method, avoiding
172221
* endless recursion by tracking which annotated elements have already been
173222
* <em>visited</em>.
223+
*
174224
* <p>The {@code metaDepth} parameter represents the depth of the annotation
175225
* relative to the initial element. For example, an annotation that is
176226
* <em>present</em> on the element will have a depth of 0; a meta-annotation
177227
* will have a depth of 1; and a meta-meta-annotation will have a depth of 2.
228+
*
178229
* @param element the annotated element
179230
* @param annotationType the annotation type to find
180-
* @param traverseClassHierarchy whether or not to traverse up the class
181-
* hierarchy recursively
231+
* @param searchInterfaces whether or not to search on interfaces, if the
232+
* annotated element is a class
233+
* @param searchClassHierarchy whether or not to search the class hierarchy
234+
* recursively, if the annotated element is a class
182235
* @param processor the processor to delegate to
183236
* @param visited the set of annotated elements that have already been visited
184237
* @param metaDepth the depth of the annotation relative to the initial element
185238
* @return the result of the processor
186239
*/
187-
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
188-
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
240+
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean searchInterfaces,
241+
boolean searchClassHierarchy, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
189242

190243
if (visited.add(element)) {
191244
try {
245+
246+
// Local annotations: either directly declared or inherited.
192247
Annotation[] annotations =
193-
(traverseClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
248+
(searchClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
249+
250+
// Search in local annotations
194251
for (Annotation annotation : annotations) {
195252
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
196253
T result = processor.process(annotation, metaDepth);
197254
if (result != null) {
198255
return result;
199256
}
200-
result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
201-
processor, visited, metaDepth + 1);
257+
result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
258+
searchClassHierarchy, processor, visited, metaDepth + 1);
202259
if (result != null) {
203260
processor.postProcess(annotation, result);
204261
return result;
205262
}
206263
}
207264
}
265+
266+
// Search in meta annotations on location annotations
208267
for (Annotation annotation : annotations) {
209268
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
210-
T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
211-
processor, visited, metaDepth);
269+
T result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
270+
searchClassHierarchy, processor, visited, metaDepth);
212271
if (result != null) {
213272
processor.postProcess(annotation, result);
214273
return result;
215274
}
216275
}
217276
}
218-
if (traverseClassHierarchy && element instanceof Class) {
277+
278+
// Search on interfaces
279+
if (searchInterfaces && element instanceof Class) {
280+
Class<?> clazz = (Class<?>) element;
281+
for (Class<?> ifc : clazz.getInterfaces()) {
282+
T result = doProcess(ifc, annotationType, searchInterfaces, searchClassHierarchy, processor,
283+
visited, metaDepth);
284+
if (result != null) {
285+
return result;
286+
}
287+
}
288+
}
289+
290+
// Search on superclass
291+
if (searchClassHierarchy && element instanceof Class) {
219292
Class<?> superclass = ((Class<?>) element).getSuperclass();
220293
if (superclass != null && !superclass.equals(Object.class)) {
221-
T result = doProcess(superclass, annotationType, true, processor, visited, metaDepth);
294+
T result = doProcess(superclass, annotationType, searchInterfaces, searchClassHierarchy,
295+
processor, visited, metaDepth);
222296
if (result != null) {
223297
return result;
224298
}

0 commit comments

Comments
 (0)