diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index 2f09a5e730..f63511bfe8 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -162,7 +162,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe } } - private boolean shouldHandle(ApplicationEvent event, Object[] args) { if (args == null) { return false; @@ -250,16 +249,11 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe protected String getCondition() { if (this.condition == null) { AnnotationAttributes annotationAttributes = AnnotatedElementUtils - .getAnnotationAttributes(this.method, EventListener.class.getName()); + .findAnnotationAttributes(this.method, EventListener.class); if (annotationAttributes != null) { String value = annotationAttributes.getString("condition"); this.condition = (value != null ? value : ""); } - // TODO [SPR-12738] Remove once AnnotatedElementUtils finds annotated methods on interfaces (e.g., in dynamic proxies) - else { - EventListener eventListener = getMethodAnnotation(EventListener.class); - this.condition = (eventListener != null ? eventListener.condition() : ""); - } } return this.condition; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 40f533bf21..6ed47b1ce9 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -18,17 +18,19 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import org.springframework.core.BridgeMethodResolver; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * Utility class used to collect all annotation attributes, including those - * declared on meta-annotations. + * Utility class used to collect and merge all annotation attributes on a + * given {@link AnnotatedElement}, including those declared via meta-annotations. * * @author Phillip Webb * @author Juergen Hoeller @@ -39,7 +41,7 @@ public class AnnotatedElementUtils { public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationType) { final Set types = new LinkedHashSet(); - process(element, annotationType, true, false, new Processor() { + processWithGetSemantics(element, annotationType, new Processor() { @Override public Object process(Annotation annotation, int metaDepth) { if (metaDepth > 0) { @@ -55,7 +57,7 @@ public class AnnotatedElementUtils { } public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) { - return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor() { + return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor() { @Override public Boolean process(Annotation annotation, int metaDepth) { if (metaDepth > 0) { @@ -70,7 +72,7 @@ public class AnnotatedElementUtils { } public static boolean isAnnotated(AnnotatedElement element, String annotationType) { - return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor() { + return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor() { @Override public Boolean process(Annotation annotation, int metaDepth) { return Boolean.TRUE; @@ -82,11 +84,16 @@ public class AnnotatedElementUtils { } /** - * Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, + * Get annotation attributes of the specified {@code annotationType} + * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * and merge the results into an {@link AnnotationAttributes} map. + * + *

Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * * @param element the annotated element * @param annotationType the annotation type to find + * @return the merged {@code AnnotationAttributes} * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) { @@ -94,62 +101,90 @@ public class AnnotatedElementUtils { } /** - * Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean)}, - * supplying {@code true} for {@code searchInterfaces} and {@code false} for {@code searchClassHierarchy}. - * - * @param element the annotated element - * @param annotationType the annotation type to find - * @param classValuesAsString whether to convert Class references into - * Strings or to preserve them as Class references - * @param nestedAnnotationsAsMap whether to turn nested Annotation instances - * into {@link AnnotationAttributes} maps or to preserve them as Annotation - * instances - * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean) - */ - public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, - boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return getAnnotationAttributes(element, annotationType, true, false, classValuesAsString, - nestedAnnotationsAsMap); - } - - /** - * Find annotation attributes of the specified {@code annotationType} in - * the annotation hierarchy of the supplied {@link AnnotatedElement}, + * Get annotation attributes of the specified {@code annotationType} + * in the annotation hierarchy of the supplied {@link AnnotatedElement}, * and merge the results into an {@link AnnotationAttributes} map. * * @param element the annotated element * @param annotationType the annotation type to find - * @param searchInterfaces whether or not to search on interfaces, if the - * annotated element is a class - * @param searchClassHierarchy whether or not to search the class hierarchy - * recursively, if the annotated element is a class * @param classValuesAsString whether to convert Class references into * Strings or to preserve them as Class references - * @param nestedAnnotationsAsMap whether to turn nested Annotation instances - * into {@link AnnotationAttributes} maps or to preserve them as Annotation - * instances + * @param nestedAnnotationsAsMap whether to convert nested Annotation + * instances into {@link AnnotationAttributes} maps or to preserve them + * as Annotation instances + * @return the merged {@code AnnotationAttributes} */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, - boolean searchInterfaces, boolean searchClassHierarchy, final boolean classValuesAsString, - final boolean nestedAnnotationsAsMap) { + boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return process(element, annotationType, searchInterfaces, searchClassHierarchy, new Processor() { - @Override - public AnnotationAttributes process(Annotation annotation, int metaDepth) { - return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap); - } - @Override - public void postProcess(Annotation annotation, AnnotationAttributes result) { - for (String key : result.keySet()) { - if (!AnnotationUtils.VALUE.equals(key)) { - Object value = AnnotationUtils.getValue(annotation, key); - if (value != null) { - result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); - } - } - } - } - }); + return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor( + classValuesAsString, nestedAnnotationsAsMap)); + } + + /** + * Find annotation attributes of the specified {@code annotationType} + * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * and merge the results into an {@link AnnotationAttributes} map. + * + *

Delegates to + * {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean, boolean, boolean)}, + * supplying {@code true} for all {@code search*} flags. + * + * @param element the annotated element + * @param annotationType the annotation type to find + * @return the merged {@code AnnotationAttributes} + */ + public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, + Class annotationType) { + return findAnnotationAttributes(element, annotationType.getName(), true, true, true, true, false, false); + } + + /** + * Find annotation attributes of the specified {@code annotationType} + * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * and merge the results into an {@link AnnotationAttributes} map. + * + *

Delegates to + * {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean, boolean, boolean)}, + * supplying {@code true} for all {@code search*} flags. + * + * @param element the annotated element + * @param annotationType the annotation type to find + * @return the merged {@code AnnotationAttributes} + */ + public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { + return findAnnotationAttributes(element, annotationType, true, true, true, true, false, false); + } + + /** + * Find annotation attributes of the specified {@code annotationType} + * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * and merge the results into an {@link AnnotationAttributes} map. + * + * @param element the annotated element + * @param annotationType the annotation type to find + * @param searchOnInterfaces whether to search on interfaces, if the + * annotated element is a class + * @param searchOnSuperclasses whether to search on superclasses, if + * the annotated element is a class + * @param searchOnMethodsInInterfaces whether to search on methods in + * interfaces, if the annotated element is a method + * @param searchOnMethodsInSuperclasses whether to search on methods + * in superclasses, if the annotated element is a method + * @param classValuesAsString whether to convert Class references into + * Strings or to preserve them as Class references + * @param nestedAnnotationsAsMap whether to convert nested Annotation + * instances into {@link AnnotationAttributes} maps or to preserve them + * as Annotation instances + * @return the merged {@code AnnotationAttributes} + */ + public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, + boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, + boolean searchOnMethodsInSuperclasses, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, + searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor( + classValuesAsString, nestedAnnotationsAsMap)); } public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationType) { @@ -160,7 +195,7 @@ public class AnnotatedElementUtils { final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { final MultiValueMap attributes = new LinkedMultiValueMap(); - process(element, annotationType, true, false, new Processor() { + processWithGetSemantics(element, annotationType, new Processor() { @Override public Void process(Annotation annotation, int metaDepth) { if (annotation.annotationType().getName().equals(annotationType)) { @@ -190,26 +225,105 @@ public class AnnotatedElementUtils { * Process all annotations of the specified {@code annotationType} and * recursively all meta-annotations on the specified {@code element}. * - *

If the {@code searchClassHierarchy} flag is {@code true} and the sought - * annotation is neither directly present on the given element nor - * present on the given element as a meta-annotation, then the algorithm will - * recursively search through the class hierarchy of the given element. - * * @param element the annotated element * @param annotationType the annotation type to find - * @param searchInterfaces whether or not to search on interfaces, if the - * annotated element is a class - * @param searchClassHierarchy whether or not to search the class hierarchy - * recursively, if the annotated element is a class * @param processor the processor to delegate to * @return the result of the processor */ - private static T process(AnnotatedElement element, String annotationType, boolean searchInterfaces, - boolean searchClassHierarchy, Processor processor) { + private static T processWithGetSemantics(AnnotatedElement element, String annotationType, Processor processor) { + try { + return processWithGetSemantics(element, annotationType, processor, new HashSet(), 0); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect annotations: " + element, ex); + } + } + + /** + * Perform the search algorithm for the {@link #processWithGetSemantics} + * method, avoiding endless recursion by tracking which annotated elements + * have already been visited. + * + *

The {@code metaDepth} parameter represents the depth of the annotation + * relative to the initial element. For example, an annotation that is + * present on the element will have a depth of 0; a meta-annotation + * will have a depth of 1; and a meta-meta-annotation will have a depth of 2. + * + * @param element the annotated element + * @param annotationType the annotation type to find + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the depth of the annotation relative to the initial element + * @return the result of the processor + */ + private static T processWithGetSemantics(AnnotatedElement element, String annotationType, + Processor processor, Set visited, int metaDepth) { + + if (visited.add(element)) { + try { + // Local annotations: declared OR inherited + Annotation[] annotations = element.getAnnotations(); + + // Search in local annotations + for (Annotation annotation : annotations) { + if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { + T result = processor.process(annotation, metaDepth); + if (result != null) { + return result; + } + result = processWithGetSemantics(annotation.annotationType(), annotationType, processor, + visited, metaDepth + 1); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + + // Search in meta annotations on local annotations + for (Annotation annotation : annotations) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor, + visited, metaDepth); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } + } + } + + } + catch (Exception ex) { + AnnotationUtils.logIntrospectionFailure(element, ex); + } + } + return null; + } + + /** + * Process all annotations of the specified {@code annotationType} and + * recursively all meta-annotations on the specified {@code element}. + * + * @param element the annotated element + * @param annotationType the annotation type to find + * @param searchOnInterfaces whether to search on interfaces, if the + * annotated element is a class + * @param searchOnSuperclasses whether to search on superclasses, if + * the annotated element is a class + * @param searchOnMethodsInInterfaces whether to search on methods in + * interfaces, if the annotated element is a method + * @param searchOnMethodsInSuperclasses whether to search on methods + * in superclasses, if the annotated element is a method + * @param processor the processor to delegate to + * @return the result of the processor + */ + private static T processWithFindSemantics(AnnotatedElement element, String annotationType, + boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, + boolean searchOnMethodsInSuperclasses, Processor processor) { try { - return doProcess(element, annotationType, searchInterfaces, searchClassHierarchy, processor, - new HashSet(), 0); + return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, + searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet(), 0); } catch (Throwable ex) { throw new IllegalStateException("Failed to introspect annotations: " + element, ex); @@ -228,24 +342,29 @@ public class AnnotatedElementUtils { * * @param element the annotated element * @param annotationType the annotation type to find - * @param searchInterfaces whether or not to search on interfaces, if the + * @param searchOnInterfaces whether to search on interfaces, if the * annotated element is a class - * @param searchClassHierarchy whether or not to search the class hierarchy - * recursively, if the annotated element is a class + * @param searchOnSuperclasses whether to search on superclasses, if + * the annotated element is a class + * @param searchOnMethodsInInterfaces whether to search on methods in + * interfaces, if the annotated element is a method + * @param searchOnMethodsInSuperclasses whether to search on methods + * in superclasses, if the annotated element is a method * @param processor the processor to delegate to * @param visited the set of annotated elements that have already been visited * @param metaDepth the depth of the annotation relative to the initial element * @return the result of the processor */ - private static T doProcess(AnnotatedElement element, String annotationType, boolean searchInterfaces, - boolean searchClassHierarchy, Processor processor, Set visited, int metaDepth) { + private static T processWithFindSemantics(AnnotatedElement element, String annotationType, + boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, + boolean searchOnMethodsInSuperclasses, Processor processor, Set visited, int metaDepth) { if (visited.add(element)) { try { // Local annotations: declared or (declared + inherited). - Annotation[] annotations = - (searchClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations()); + Annotation[] annotations = (searchOnSuperclasses ? element.getDeclaredAnnotations() + : element.getAnnotations()); // Search in local annotations for (Annotation annotation : annotations) { @@ -254,8 +373,9 @@ public class AnnotatedElementUtils { if (result != null) { return result; } - result = doProcess(annotation.annotationType(), annotationType, searchInterfaces, - searchClassHierarchy, processor, visited, metaDepth + 1); + result = processWithFindSemantics(annotation.annotationType(), annotationType, + searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, + searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1); if (result != null) { processor.postProcess(annotation, result); return result; @@ -266,8 +386,9 @@ public class AnnotatedElementUtils { // Search in meta annotations on local annotations for (Annotation annotation : annotations) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { - T result = doProcess(annotation.annotationType(), annotationType, searchInterfaces, - searchClassHierarchy, processor, visited, metaDepth); + T result = processWithFindSemantics(annotation.annotationType(), annotationType, + searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, + searchOnMethodsInSuperclasses, processor, visited, metaDepth); if (result != null) { processor.postProcess(annotation, result); return result; @@ -275,26 +396,93 @@ public class AnnotatedElementUtils { } } - // Search on interfaces - if (searchInterfaces && element instanceof Class) { - Class clazz = (Class) element; - for (Class ifc : clazz.getInterfaces()) { - T result = doProcess(ifc, annotationType, searchInterfaces, searchClassHierarchy, processor, - visited, metaDepth); + if (element instanceof Method) { + Method method = (Method) element; + + // Search on possibly bridged method + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + T result = processWithFindSemantics(resolvedMethod, annotationType, searchOnInterfaces, + searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, + visited, metaDepth); + if (result != null) { + return result; + } + + // Search on methods in interfaces declared locally + if (searchOnMethodsInInterfaces) { + Class[] ifcs = method.getDeclaringClass().getInterfaces(); + result = searchOnInterfaces(method, annotationType, searchOnInterfaces, searchOnSuperclasses, + searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth, + ifcs); if (result != null) { return result; } } + + // Search on methods in class hierarchy and interface hierarchy + if (searchOnMethodsInSuperclasses) { + Class clazz = method.getDeclaringClass(); + while (true) { + clazz = clazz.getSuperclass(); + if (clazz == null || clazz.equals(Object.class)) { + break; + } + + try { + // TODO [SPR-12738] Resolve equivalent parameterized + // method (i.e., bridged method) in superclass. + Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), + method.getParameterTypes()); + Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod); + result = processWithFindSemantics(resolvedEquivalentMethod, annotationType, + searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, + searchOnMethodsInSuperclasses, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + catch (NoSuchMethodException ex) { + // No equivalent method found + } + + // Search on interfaces declared on superclass + if (searchOnMethodsInInterfaces) { + result = searchOnInterfaces(method, annotationType, searchOnInterfaces, + searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, + processor, visited, metaDepth, clazz.getInterfaces()); + if (result != null) { + return result; + } + } + } + } } - // Search on superclass - if (searchClassHierarchy && element instanceof Class) { - Class superclass = ((Class) element).getSuperclass(); - if (superclass != null && !superclass.equals(Object.class)) { - T result = doProcess(superclass, annotationType, searchInterfaces, searchClassHierarchy, - processor, visited, metaDepth); - if (result != null) { - return result; + if (element instanceof Class) { + Class clazz = (Class) element; + + // Search on interfaces + if (searchOnInterfaces) { + for (Class ifc : clazz.getInterfaces()) { + T result = processWithFindSemantics(ifc, annotationType, searchOnInterfaces, + searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, + processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + + // Search on superclass + if (searchOnSuperclasses) { + Class superclass = clazz.getSuperclass(); + if (superclass != null && !superclass.equals(Object.class)) { + T result = processWithFindSemantics(superclass, annotationType, searchOnInterfaces, + searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, + processor, visited, metaDepth); + if (result != null) { + return result; + } } } } @@ -306,27 +494,99 @@ public class AnnotatedElementUtils { return null; } + private static T searchOnInterfaces(Method method, String annotationType, boolean searchOnInterfaces, + boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses, + Processor processor, Set visited, int metaDepth, Class[] ifcs) { + + for (Class iface : ifcs) { + if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) { + try { + Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); + T result = processWithFindSemantics(equivalentMethod, annotationType, searchOnInterfaces, + searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, + visited, metaDepth); + + if (result != null) { + return result; + } + } + catch (NoSuchMethodException ex) { + // Skip this interface - it doesn't have the method... + } + } + } + + return null; + } + /** - * Callback interface used to process an annotation. + * Callback interface that is used to process a target annotation that + * was found as the result of a search and to post-process the result as + * the search algorithm goes back down the annotation hierarchy from + * the target annotation to the initial {@link AnnotatedElement}. + * * @param the result type */ - private interface Processor { + private static interface Processor { /** - * Called to process the annotation. + * Process the actual target annotation once it has been found by + * the search algorithm. + * *

The {@code metaDepth} parameter represents the depth of the * annotation relative to the initial element. For example, an annotation * that is present on the element will have a depth of 0; a * meta-annotation will have a depth of 1; and a meta-meta-annotation * will have a depth of 2. + * * @param annotation the annotation to process * @param metaDepth the depth of the annotation relative to the initial element * @return the result of the processing, or {@code null} to continue */ T process(Annotation annotation, int metaDepth); + /** + * Post-process the result returned by the {@link #process} method. + * + *

The {@code annotation} supplied to this method is an annotation + * that is present in the annotation hierarchy, above the initial + * {@link AnnotatedElement} but below the target annotation found by + * the search algorithm. + * + * @param annotation the annotation to post-process + * @param result the result to post-process + */ void postProcess(Annotation annotation, T result); } + private static class MergeAnnotationAttributesProcessor implements Processor { + + private final boolean classValuesAsString; + private final boolean nestedAnnotationsAsMap; + + + MergeAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + this.classValuesAsString = classValuesAsString; + this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; + } + + @Override + public AnnotationAttributes process(Annotation annotation, int metaDepth) { + return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap); + } + + @Override + public void postProcess(Annotation annotation, AnnotationAttributes result) { + for (String key : result.keySet()) { + if (!AnnotationUtils.VALUE.equals(key)) { + Object value = AnnotationUtils.getValue(annotation, key); + if (value != null) { + result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); + } + } + } + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 439cb9d124..c5969b9bac 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -100,9 +100,9 @@ public abstract class AnnotationUtils { * @since 4.0 */ @SuppressWarnings("unchecked") - public static T getAnnotation(Annotation ann, Class annotationType) { + public static A getAnnotation(Annotation ann, Class annotationType) { if (annotationType.isInstance(ann)) { - return (T) ann; + return (A) ann; } try { return ann.annotationType().getAnnotation(annotationType); @@ -126,9 +126,9 @@ public abstract class AnnotationUtils { * @return the matching annotation, or {@code null} if not found * @since 3.1 */ - public static T getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + public static A getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { try { - T ann = annotatedElement.getAnnotation(annotationType); + A ann = annotatedElement.getAnnotation(annotationType); if (ann == null) { for (Annotation metaAnn : annotatedElement.getAnnotations()) { ann = metaAnn.annotationType().getAnnotation(annotationType); @@ -294,18 +294,18 @@ public abstract class AnnotationUtils { * @since 4.2 */ @SuppressWarnings("unchecked") - private static T findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) { + private static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) { Assert.notNull(annotatedElement, "AnnotatedElement must not be null"); try { Annotation[] anns = annotatedElement.getDeclaredAnnotations(); for (Annotation ann : anns) { if (ann.annotationType().equals(annotationType)) { - return (T) ann; + return (A) ann; } } for (Annotation ann : anns) { if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { - T annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited); + A annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited); if (annotation != null) { return annotation; } @@ -392,16 +392,16 @@ public abstract class AnnotationUtils { return annotation; } - private static boolean isInterfaceWithAnnotatedMethods(Class iface) { + static boolean isInterfaceWithAnnotatedMethods(Class iface) { Boolean flag = annotatedInterfaceCache.get(iface); if (flag != null) { - return flag; + return flag.booleanValue(); } - boolean found = false; + Boolean found = Boolean.FALSE; for (Method ifcMethod : iface.getMethods()) { try { if (ifcMethod.getAnnotations().length > 0) { - found = true; + found = Boolean.TRUE; break; } } @@ -411,7 +411,7 @@ public abstract class AnnotationUtils { } } annotatedInterfaceCache.put(iface, found); - return found; + return found.booleanValue(); } /** diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 4aa8d4f55e..a7b7f1265f 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -126,54 +126,6 @@ public class AnnotatedElementUtilsTests { attributes.getBoolean("readOnly")); } - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnInheritedAnnotationInterface() { - String name = Transactional.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(InheritedAnnotationInterface.class, name); - assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes); - } - - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnSubInheritedAnnotationInterface() { - String name = Transactional.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(SubInheritedAnnotationInterface.class, name); - assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", attributes); - } - - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnSubSubInheritedAnnotationInterface() { - String name = Transactional.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(SubSubInheritedAnnotationInterface.class, name); - assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", attributes); - } - - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnNonInheritedAnnotationInterface() { - String name = Order.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(NonInheritedAnnotationInterface.class, name); - assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes); - } - - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnSubNonInheritedAnnotationInterface() { - String name = Order.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(SubNonInheritedAnnotationInterface.class, name); - assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes); - } - - /** @since 4.2 */ - @Test - public void getAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() { - String name = Order.class.getName(); - AnnotationAttributes attributes = getAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, name); - assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes); - } - // TODO [SPR-11598] Enable test. @Ignore("Disabled until SPR-11598 is resolved") @Test @@ -183,37 +135,126 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes); } - // TODO [SPR-12738] Enable test. - @Ignore("Disabled until SPR-12738 is resolved") + /** @since 4.2 */ @Test - public void getAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException { - String name = Order.class.getName(); - Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface"); - AnnotationAttributes attributes = getAnnotationAttributes(method, name); - assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method", - attributes); + public void getAnnotationAttributesOnInheritedAnnotationInterface() { + String name = Transactional.class.getName(); + AnnotationAttributes attributes = getAnnotationAttributes(InheritedAnnotationInterface.class, name); + assertNotNull("Should get @Transactional on InheritedAnnotationInterface", attributes); } - // TODO [SPR-12738] Enable test. - @Ignore("Disabled until SPR-12738 is resolved") + /** @since 4.2 */ @Test - public void getAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException { - String name = Transactional.class.getName(); + public void findAnnotationAttributesOnInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class); + assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnSubInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(SubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnSubSubInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnNonInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class); + assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void getAnnotationAttributesOnNonInheritedAnnotationInterface() { + AnnotationAttributes attributes = getAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class.getName()); + assertNotNull("Should get @Order on NonInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnSubNonInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(SubNonInheritedAnnotationInterface.class, Order.class); + assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() { + AnnotationAttributes attributes = findAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, Order.class); + assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException { + Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface"); + AnnotationAttributes attributes = findAnnotationAttributes(method, Order.class); + assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method", attributes); + } + + /** @since 4.2 */ + @Test + public void findAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException { Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle"); - AnnotationAttributes attributes = getAnnotationAttributes(method, name); + AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.class); assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handle() method", attributes); } - // TODO [SPR-12738] Enable test. + /** + * TODO [SPR-12738] Enable test. + * + *

{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas, + * {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}. + * + *

Thus, this test fails because {@code AnnotatedElementUtils.processWithFindSemantics()} + * does not resolve an equivalent method for {@code handleParameterized(String)} + * in {@code AbstractClassWithInheritedAnnotation}. + * + * @since 4.2 + */ @Ignore("Disabled until SPR-12738 is resolved") @Test - public void getAnnotationAttributesInheritedFromParameterizedMethod() throws NoSuchMethodException { - String name = Transactional.class.getName(); + public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException { Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class); - AnnotationAttributes attributes = getAnnotationAttributes(method, name); + AnnotationAttributes attributes = findAnnotationAttributes(method, Transactional.class); assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handleParameterized() method", attributes); } + /** + * Bridge/bridged method setup code copied from + * {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}. + * @since 4.2 + */ + @Test + public void findAnnotationAttributesFromBridgeMethod() throws NoSuchMethodException { + Method[] methods = StringGenericParameter.class.getMethods(); + Method bridgeMethod = null; + Method bridgedMethod = null; + for (Method method : methods) { + if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) { + if (method.getReturnType().equals(Object.class)) { + bridgeMethod = method; + } + else { + bridgedMethod = method; + } + } + } + assertTrue(bridgeMethod != null && bridgeMethod.isBridge()); + assertTrue(bridgedMethod != null && !bridgedMethod.isBridge()); + + AnnotationAttributes attributes = findAnnotationAttributes(bridgeMethod, Order.class); + assertNotNull("Should find @Order on StringGenericParameter.getFor() method", attributes); + } + // ------------------------------------------------------------------------- @@ -349,6 +390,26 @@ public class AnnotatedElementUtilsTests { } } + public static interface GenericParameter { + + T getFor(Class cls); + } + + @SuppressWarnings("unused") + private static class StringGenericParameter implements GenericParameter { + + @Order + @Override + public String getFor(Class cls) { + return "foo"; + } + + public String getFor(Integer integer) { + return "foo"; + } + } + + @Transactional public static interface InheritedAnnotationInterface { } diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index c38d53479b..87daf46c02 100644 --- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -286,8 +286,8 @@ public abstract class MetaAnnotationUtils { this.declaringClass = declaringClass; this.composedAnnotation = composedAnnotation; this.annotation = annotation; - this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass, - annotation.annotationType().getName(), true, true, false, false); + this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes( + rootDeclaringClass, annotation.annotationType()); } public Class getRootDeclaringClass() { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java index a175bf97db..8b05331450 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/JtaTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import org.springframework.transaction.interceptor.TransactionAttribute; * Strategy implementation for parsing JTA 1.2's {@link javax.transaction.Transactional} annotation. * * @author Juergen Hoeller - * @author Sam Brannen * @since 4.0 */ @SuppressWarnings("serial") @@ -40,8 +39,7 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars @Override public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { - AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, - javax.transaction.Transactional.class.getName(), false, false, false, false); + AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, javax.transaction.Transactional.class.getName()); if (ann != null) { return parseTransactionAnnotation(ann); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index 4cc2aa2ded..60ae9b8fda 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import org.springframework.transaction.interceptor.TransactionAttribute; * Strategy implementation for parsing Spring's {@link Transactional} annotation. * * @author Juergen Hoeller - * @author Sam Brannen * @since 2.5 */ @SuppressWarnings("serial") @@ -40,8 +39,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP @Override public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) { - AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName(), - false, false, false, false); + AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName()); if (ann != null) { return parseTransactionAnnotation(ann); } diff --git a/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java b/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java index e8b5cfb403..37414100be 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/event/TransactionalEventListenerTests.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import org.junit.After; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -150,16 +149,13 @@ public class TransactionalEventListenerTests { getEventCollector().assertTotalEventsCount(1); // After rollback not invoked } - // TODO [SPR-12738] Enable test. - @Ignore("Disabled until SPR-12738 is resolved") @Test public void afterCommitWithTransactionalComponentListenerProxiedViaDynamicProxy() { - load(TransactionalConfiguration.class, TransactionalComponentAfterCommitTestListener.class); + load(TransactionalConfiguration.class, TransactionalComponentTestListener.class); this.transactionTemplate.execute(status -> { getContext().publishEvent("SKIP"); getEventCollector().assertNoEventReceived(); return null; - }); getEventCollector().assertNoEventReceived(); } @@ -280,7 +276,6 @@ public class TransactionalEventListenerTests { getContext().publishEvent("SKIP"); getEventCollector().assertNoEventReceived(); return null; - }); getEventCollector().assertNoEventReceived(); } @@ -460,14 +455,15 @@ public class TransactionalEventListenerTests { @Transactional @Component - static interface TransactionalComponentAfterCommitTestListenerInterface { + static interface TransactionalComponentTestListenerInterface { - @TransactionalEventListener(phase = AFTER_COMMIT, condition = "!'SKIP'.equals(#data)") + // Cannot use #data in condition due to dynamic proxy. + @TransactionalEventListener(condition = "!'SKIP'.equals(#p0)") void handleAfterCommit(String data); } - static class TransactionalComponentAfterCommitTestListener extends BaseTransactionalTestListener implements - TransactionalComponentAfterCommitTestListenerInterface { + static class TransactionalComponentTestListener extends BaseTransactionalTestListener implements + TransactionalComponentTestListenerInterface { @Override public void handleAfterCommit(String data) {