Skip to content

Commit ad6bea1

Browse files
committed
Support abstract, bridge, & interface methods in AnnotatedElementUtils
This commit introduces support for finding annotations on abstract, bridge, and interface methods in AnnotatedElementUtils. - Introduced dedicated findAnnotationAttributes() methods in AnnotatedElementUtils that provide first-class support for processing methods, class hierarchies, interfaces, bridge methods, etc. - Introduced find/get search algorithm dichotomy in AnnotatedElementUtils which is visible in the public API as well as in the internal implementation. This was necessary in order to maintain backwards compatibility with the existing API (even though it was undocumented). - Reverted all recent changes made to the "get semantics" search algorithm in AnnotatedElementUtils in order to ensure backwards compatibility, and reverted recent changes to JtaTransactionAnnotationParser and SpringTransactionAnnotationParser accordingly. - Documented internal AnnotatedElementUtils.Processor<T> interface. - Enabled failing tests and introduced findAnnotationAttributesFromBridgeMethod() test in AnnotatedElementUtilsTests. - Refactored ApplicationListenerMethodAdapter.getCondition() and enabled failing test in TransactionalEventListenerTests. - AnnotationUtils.isInterfaceWithAnnotatedMethods() is now package private. Issue: SPR-12738, SPR-11514, SPR-11598
1 parent ececf32 commit ad6bea1

File tree

8 files changed

+467
-160
lines changed

8 files changed

+467
-160
lines changed

spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ private void publishEvent(Object event) {
162162
}
163163
}
164164

165-
166165
private boolean shouldHandle(ApplicationEvent event, Object[] args) {
167166
if (args == null) {
168167
return false;
@@ -250,16 +249,11 @@ protected Object getTargetBean() {
250249
protected String getCondition() {
251250
if (this.condition == null) {
252251
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
253-
.getAnnotationAttributes(this.method, EventListener.class.getName());
252+
.findAnnotationAttributes(this.method, EventListener.class);
254253
if (annotationAttributes != null) {
255254
String value = annotationAttributes.getString("condition");
256255
this.condition = (value != null ? value : "");
257256
}
258-
// TODO [SPR-12738] Remove once AnnotatedElementUtils finds annotated methods on interfaces (e.g., in dynamic proxies)
259-
else {
260-
EventListener eventListener = getMethodAnnotation(EventListener.class);
261-
this.condition = (eventListener != null ? eventListener.condition() : "");
262-
}
263257
}
264258
return this.condition;
265259
}

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

Lines changed: 342 additions & 82 deletions
Large diffs are not rendered by default.

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ public abstract class AnnotationUtils {
100100
* @since 4.0
101101
*/
102102
@SuppressWarnings("unchecked")
103-
public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) {
103+
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
104104
if (annotationType.isInstance(ann)) {
105-
return (T) ann;
105+
return (A) ann;
106106
}
107107
try {
108108
return ann.annotationType().getAnnotation(annotationType);
@@ -126,9 +126,9 @@ public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> an
126126
* @return the matching annotation, or {@code null} if not found
127127
* @since 3.1
128128
*/
129-
public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) {
129+
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
130130
try {
131-
T ann = annotatedElement.getAnnotation(annotationType);
131+
A ann = annotatedElement.getAnnotation(annotationType);
132132
if (ann == null) {
133133
for (Annotation metaAnn : annotatedElement.getAnnotations()) {
134134
ann = metaAnn.annotationType().getAnnotation(annotationType);
@@ -294,18 +294,18 @@ public static <A extends Annotation> A findAnnotation(AnnotatedElement annotated
294294
* @since 4.2
295295
*/
296296
@SuppressWarnings("unchecked")
297-
private static <T extends Annotation> T findAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType, Set<Annotation> visited) {
297+
private static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
298298
Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
299299
try {
300300
Annotation[] anns = annotatedElement.getDeclaredAnnotations();
301301
for (Annotation ann : anns) {
302302
if (ann.annotationType().equals(annotationType)) {
303-
return (T) ann;
303+
return (A) ann;
304304
}
305305
}
306306
for (Annotation ann : anns) {
307307
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
308-
T annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited);
308+
A annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited);
309309
if (annotation != null) {
310310
return annotation;
311311
}
@@ -392,16 +392,16 @@ private static <A extends Annotation> A searchOnInterfaces(Method method, Class<
392392
return annotation;
393393
}
394394

395-
private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
395+
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
396396
Boolean flag = annotatedInterfaceCache.get(iface);
397397
if (flag != null) {
398-
return flag;
398+
return flag.booleanValue();
399399
}
400-
boolean found = false;
400+
Boolean found = Boolean.FALSE;
401401
for (Method ifcMethod : iface.getMethods()) {
402402
try {
403403
if (ifcMethod.getAnnotations().length > 0) {
404-
found = true;
404+
found = Boolean.TRUE;
405405
break;
406406
}
407407
}
@@ -411,7 +411,7 @@ private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
411411
}
412412
}
413413
annotatedInterfaceCache.put(iface, found);
414-
return found;
414+
return found.booleanValue();
415415
}
416416

417417
/**

spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

Lines changed: 100 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -126,94 +126,135 @@ public void getAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLoc
126126
attributes.getBoolean("readOnly"));
127127
}
128128

129+
// TODO [SPR-11598] Enable test.
130+
@Ignore("Disabled until SPR-11598 is resolved")
131+
@Test
132+
public void getAnnotationAttributesFromInterfaceImplementedBySuperclass() {
133+
String name = Transactional.class.getName();
134+
AnnotationAttributes attributes = getAnnotationAttributes(ConcreteClassWithInheritedAnnotation.class, name);
135+
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes);
136+
}
137+
129138
/** @since 4.2 */
130139
@Test
131140
public void getAnnotationAttributesOnInheritedAnnotationInterface() {
132141
String name = Transactional.class.getName();
133142
AnnotationAttributes attributes = getAnnotationAttributes(InheritedAnnotationInterface.class, name);
143+
assertNotNull("Should get @Transactional on InheritedAnnotationInterface", attributes);
144+
}
145+
146+
/** @since 4.2 */
147+
@Test
148+
public void findAnnotationAttributesOnInheritedAnnotationInterface() {
149+
AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class);
134150
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes);
135151
}
136152

137153
/** @since 4.2 */
138154
@Test
139-
public void getAnnotationAttributesOnSubInheritedAnnotationInterface() {
140-
String name = Transactional.class.getName();
141-
AnnotationAttributes attributes = getAnnotationAttributes(SubInheritedAnnotationInterface.class, name);
155+
public void findAnnotationAttributesOnSubInheritedAnnotationInterface() {
156+
AnnotationAttributes attributes = findAnnotationAttributes(SubInheritedAnnotationInterface.class, Transactional.class);
142157
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", attributes);
143158
}
144159

145160
/** @since 4.2 */
146161
@Test
147-
public void getAnnotationAttributesOnSubSubInheritedAnnotationInterface() {
148-
String name = Transactional.class.getName();
149-
AnnotationAttributes attributes = getAnnotationAttributes(SubSubInheritedAnnotationInterface.class, name);
162+
public void findAnnotationAttributesOnSubSubInheritedAnnotationInterface() {
163+
AnnotationAttributes attributes = findAnnotationAttributes(SubSubInheritedAnnotationInterface.class, Transactional.class);
150164
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", attributes);
151165
}
152166

153167
/** @since 4.2 */
154168
@Test
155-
public void getAnnotationAttributesOnNonInheritedAnnotationInterface() {
156-
String name = Order.class.getName();
157-
AnnotationAttributes attributes = getAnnotationAttributes(NonInheritedAnnotationInterface.class, name);
169+
public void findAnnotationAttributesOnNonInheritedAnnotationInterface() {
170+
AnnotationAttributes attributes = findAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class);
158171
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes);
159172
}
160173

161174
/** @since 4.2 */
162175
@Test
163-
public void getAnnotationAttributesOnSubNonInheritedAnnotationInterface() {
164-
String name = Order.class.getName();
165-
AnnotationAttributes attributes = getAnnotationAttributes(SubNonInheritedAnnotationInterface.class, name);
166-
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes);
176+
public void getAnnotationAttributesOnNonInheritedAnnotationInterface() {
177+
AnnotationAttributes attributes = getAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class.getName());
178+
assertNotNull("Should get @Order on NonInheritedAnnotationInterface", attributes);
167179
}
168180

169181
/** @since 4.2 */
170182
@Test
171-
public void getAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() {
172-
String name = Order.class.getName();
173-
AnnotationAttributes attributes = getAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, name);
174-
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes);
183+
public void findAnnotationAttributesOnSubNonInheritedAnnotationInterface() {
184+
AnnotationAttributes attributes = findAnnotationAttributes(SubNonInheritedAnnotationInterface.class, Order.class);
185+
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes);
175186
}
176187

177-
// TODO [SPR-11598] Enable test.
178-
@Ignore("Disabled until SPR-11598 is resolved")
188+
/** @since 4.2 */
179189
@Test
180-
public void getAnnotationAttributesFromInterfaceImplementedBySuperclass() {
181-
String name = Transactional.class.getName();
182-
AnnotationAttributes attributes = getAnnotationAttributes(ConcreteClassWithInheritedAnnotation.class, name);
183-
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes);
190+
public void findAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() {
191+
AnnotationAttributes attributes = findAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, Order.class);
192+
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes);
184193
}
185194

186-
// TODO [SPR-12738] Enable test.
187-
@Ignore("Disabled until SPR-12738 is resolved")
195+
/** @since 4.2 */
188196
@Test
189-
public void getAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException {
190-
String name = Order.class.getName();
197+
public void findAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException {
191198
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface");
192-
AnnotationAttributes attributes = getAnnotationAttributes(method, name);
193-
assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method",
194-
attributes);
199+
AnnotationAttributes attributes = findAnnotationAttributes(method, Order.class);
200+
assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method", attributes);
195201
}
196202

197-
// TODO [SPR-12738] Enable test.
198-
@Ignore("Disabled until SPR-12738 is resolved")
203+
/** @since 4.2 */
199204
@Test
200-
public void getAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException {
201-
String name = Transactional.class.getName();
205+
public void findAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException {
202206
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle");
203-
AnnotationAttributes attributes = getAnnotationAttributes(method, name);
207+
AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.class);
204208
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handle() method", attributes);
205209
}
206210

207-
// TODO [SPR-12738] Enable test.
211+
/**
212+
* TODO [SPR-12738] Enable test.
213+
*
214+
* <p>{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas,
215+
* {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}.
216+
*
217+
* <p>Thus, this test fails because {@code AnnotatedElementUtils.processWithFindSemantics()}
218+
* does not resolve an equivalent method for {@code handleParameterized(String)}
219+
* in {@code AbstractClassWithInheritedAnnotation}.
220+
*
221+
* @since 4.2
222+
*/
208223
@Ignore("Disabled until SPR-12738 is resolved")
209224
@Test
210-
public void getAnnotationAttributesInheritedFromParameterizedMethod() throws NoSuchMethodException {
211-
String name = Transactional.class.getName();
225+
public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
212226
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
213-
AnnotationAttributes attributes = getAnnotationAttributes(method, name);
227+
AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.class);
214228
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handleParameterized() method", attributes);
215229
}
216230

231+
/**
232+
* Bridge/bridged method setup code copied from
233+
* {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}.
234+
* @since 4.2
235+
*/
236+
@Test
237+
public void findAnnotationAttributesFromBridgeMethod() throws NoSuchMethodException {
238+
Method[] methods = StringGenericParameter.class.getMethods();
239+
Method bridgeMethod = null;
240+
Method bridgedMethod = null;
241+
for (Method method : methods) {
242+
if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
243+
if (method.getReturnType().equals(Object.class)) {
244+
bridgeMethod = method;
245+
}
246+
else {
247+
bridgedMethod = method;
248+
}
249+
}
250+
}
251+
assertTrue(bridgeMethod != null && bridgeMethod.isBridge());
252+
assertTrue(bridgedMethod != null && !bridgedMethod.isBridge());
253+
254+
AnnotationAttributes attributes = findAnnotationAttributes(bridgeMethod, Order.class);
255+
assertNotNull("Should find @Order on StringGenericParameter.getFor() method", attributes);
256+
}
257+
217258

218259
// -------------------------------------------------------------------------
219260

@@ -349,6 +390,26 @@ public void handleFromInterface() {
349390
}
350391
}
351392

393+
public static interface GenericParameter<T> {
394+
395+
T getFor(Class<T> cls);
396+
}
397+
398+
@SuppressWarnings("unused")
399+
private static class StringGenericParameter implements GenericParameter<String> {
400+
401+
@Order
402+
@Override
403+
public String getFor(Class<String> cls) {
404+
return "foo";
405+
}
406+
407+
public String getFor(Integer integer) {
408+
return "foo";
409+
}
410+
}
411+
412+
352413
@Transactional
353414
public static interface InheritedAnnotationInterface {
354415
}

spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,8 @@ public AnnotationDescriptor(Class<?> rootDeclaringClass, Class<?> declaringClass
286286
this.declaringClass = declaringClass;
287287
this.composedAnnotation = composedAnnotation;
288288
this.annotation = annotation;
289-
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
290-
annotation.annotationType().getName(), true, true, false, false);
289+
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(
290+
rootDeclaringClass, annotation.annotationType());
291291
}
292292

293293
public Class<?> getRootDeclaringClass() {

spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 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.
@@ -32,16 +32,14 @@
3232
* Strategy implementation for parsing JTA 1.2's {@link javax.transaction.Transactional} annotation.
3333
*
3434
* @author Juergen Hoeller
35-
* @author Sam Brannen
3635
* @since 4.0
3736
*/
3837
@SuppressWarnings("serial")
3938
public class JtaTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {
4039

4140
@Override
4241
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
43-
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae,
44-
javax.transaction.Transactional.class.getName(), false, false, false, false);
42+
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, javax.transaction.Transactional.class.getName());
4543
if (ann != null) {
4644
return parseTransactionAnnotation(ann);
4745
}

spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 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.
@@ -32,16 +32,14 @@
3232
* Strategy implementation for parsing Spring's {@link Transactional} annotation.
3333
*
3434
* @author Juergen Hoeller
35-
* @author Sam Brannen
3635
* @since 2.5
3736
*/
3837
@SuppressWarnings("serial")
3938
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {
4039

4140
@Override
4241
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
43-
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName(),
44-
false, false, false, false);
42+
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName());
4543
if (ann != null) {
4644
return parseTransactionAnnotation(ann);
4745
}

0 commit comments

Comments
 (0)