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
This commit is contained in:
parent
ececf32c05
commit
ad6bea1cda
|
@ -162,7 +162,6 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean shouldHandle(ApplicationEvent event, Object[] args) {
|
private boolean shouldHandle(ApplicationEvent event, Object[] args) {
|
||||||
if (args == null) {
|
if (args == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -250,16 +249,11 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||||
protected String getCondition() {
|
protected String getCondition() {
|
||||||
if (this.condition == null) {
|
if (this.condition == null) {
|
||||||
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
|
||||||
.getAnnotationAttributes(this.method, EventListener.class.getName());
|
.findAnnotationAttributes(this.method, EventListener.class);
|
||||||
if (annotationAttributes != null) {
|
if (annotationAttributes != null) {
|
||||||
String value = annotationAttributes.getString("condition");
|
String value = annotationAttributes.getString("condition");
|
||||||
this.condition = (value != null ? value : "");
|
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;
|
return this.condition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,19 @@ package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.core.BridgeMethodResolver;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class used to collect all annotation attributes, including those
|
* Utility class used to collect and merge all annotation attributes on a
|
||||||
* declared on meta-annotations.
|
* given {@link AnnotatedElement}, including those declared via meta-annotations.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
@ -39,7 +41,7 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||||
final Set<String> types = new LinkedHashSet<String>();
|
final Set<String> types = new LinkedHashSet<String>();
|
||||||
process(element, annotationType, true, false, new Processor<Object>() {
|
processWithGetSemantics(element, annotationType, new Processor<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object process(Annotation annotation, int metaDepth) {
|
public Object process(Annotation annotation, int metaDepth) {
|
||||||
if (metaDepth > 0) {
|
if (metaDepth > 0) {
|
||||||
|
@ -55,7 +57,7 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||||
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
|
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean process(Annotation annotation, int metaDepth) {
|
public Boolean process(Annotation annotation, int metaDepth) {
|
||||||
if (metaDepth > 0) {
|
if (metaDepth > 0) {
|
||||||
|
@ -70,7 +72,7 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
|
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
|
||||||
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
|
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean process(Annotation annotation, int metaDepth) {
|
public Boolean process(Annotation annotation, int metaDepth) {
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
|
@ -82,11 +84,16 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
|
* <em>Get</em> 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.
|
||||||
|
*
|
||||||
|
* <p>Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
|
||||||
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
|
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
|
||||||
*
|
*
|
||||||
* @param element the annotated element
|
* @param element the annotated element
|
||||||
* @param annotationType the annotation type to find
|
* @param annotationType the annotation type to find
|
||||||
|
* @return the merged {@code AnnotationAttributes}
|
||||||
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
|
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)},
|
* <em>Get</em> annotation attributes of the specified {@code annotationType}
|
||||||
* supplying {@code true} for {@code searchInterfaces} and {@code false} for {@code searchClassHierarchy}.
|
* in the annotation hierarchy of the supplied {@link AnnotatedElement},
|
||||||
*
|
|
||||||
* @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},
|
|
||||||
* and merge the results into an {@link AnnotationAttributes} map.
|
* and merge the results into an {@link AnnotationAttributes} map.
|
||||||
*
|
*
|
||||||
* @param element the annotated element
|
* @param element the annotated element
|
||||||
* @param annotationType the annotation type to find
|
* @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
|
* @param classValuesAsString whether to convert Class references into
|
||||||
* Strings or to preserve them as Class references
|
* Strings or to preserve them as Class references
|
||||||
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances
|
* @param nestedAnnotationsAsMap whether to convert nested Annotation
|
||||||
* into {@link AnnotationAttributes} maps or to preserve them as Annotation
|
* instances into {@link AnnotationAttributes} maps or to preserve them
|
||||||
* instances
|
* as Annotation instances
|
||||||
|
* @return the merged {@code AnnotationAttributes}
|
||||||
*/
|
*/
|
||||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||||
boolean searchInterfaces, boolean searchClassHierarchy, final boolean classValuesAsString,
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
final boolean nestedAnnotationsAsMap) {
|
|
||||||
|
|
||||||
return process(element, annotationType, searchInterfaces, searchClassHierarchy, new Processor<AnnotationAttributes>() {
|
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(
|
||||||
@Override
|
classValuesAsString, nestedAnnotationsAsMap));
|
||||||
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
|
}
|
||||||
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
|
|
||||||
}
|
/**
|
||||||
@Override
|
* <em>Find</em> annotation attributes of the specified {@code annotationType}
|
||||||
public void postProcess(Annotation annotation, AnnotationAttributes result) {
|
* in the annotation hierarchy of the supplied {@link AnnotatedElement},
|
||||||
for (String key : result.keySet()) {
|
* and merge the results into an {@link AnnotationAttributes} map.
|
||||||
if (!AnnotationUtils.VALUE.equals(key)) {
|
*
|
||||||
Object value = AnnotationUtils.getValue(annotation, key);
|
* <p>Delegates to
|
||||||
if (value != null) {
|
* {@link #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean, boolean, boolean)},
|
||||||
result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
|
* 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<? extends Annotation> annotationType) {
|
||||||
|
return findAnnotationAttributes(element, annotationType.getName(), true, true, true, true, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <em>Find</em> 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.
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <em>Find</em> 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<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationType) {
|
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationType) {
|
||||||
|
@ -160,7 +195,7 @@ public class AnnotatedElementUtils {
|
||||||
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
|
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
|
||||||
process(element, annotationType, true, false, new Processor<Void>() {
|
processWithGetSemantics(element, annotationType, new Processor<Void>() {
|
||||||
@Override
|
@Override
|
||||||
public Void process(Annotation annotation, int metaDepth) {
|
public Void process(Annotation annotation, int metaDepth) {
|
||||||
if (annotation.annotationType().getName().equals(annotationType)) {
|
if (annotation.annotationType().getName().equals(annotationType)) {
|
||||||
|
@ -190,26 +225,105 @@ public class AnnotatedElementUtils {
|
||||||
* Process all annotations of the specified {@code annotationType} and
|
* Process all annotations of the specified {@code annotationType} and
|
||||||
* recursively all meta-annotations on the specified {@code element}.
|
* recursively all meta-annotations on the specified {@code element}.
|
||||||
*
|
*
|
||||||
* <p>If the {@code searchClassHierarchy} flag is {@code true} and the sought
|
|
||||||
* annotation is neither <em>directly present</em> 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 element the annotated element
|
||||||
* @param annotationType the annotation type to find
|
* @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
|
* @param processor the processor to delegate to
|
||||||
* @return the result of the processor
|
* @return the result of the processor
|
||||||
*/
|
*/
|
||||||
private static <T> T process(AnnotatedElement element, String annotationType, boolean searchInterfaces,
|
private static <T> T processWithGetSemantics(AnnotatedElement element, String annotationType, Processor<T> processor) {
|
||||||
boolean searchClassHierarchy, Processor<T> processor) {
|
try {
|
||||||
|
return processWithGetSemantics(element, annotationType, processor, new HashSet<AnnotatedElement>(), 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 <em>visited</em>.
|
||||||
|
*
|
||||||
|
* <p>The {@code metaDepth} parameter represents the depth of the annotation
|
||||||
|
* relative to the initial element. For example, an annotation that is
|
||||||
|
* <em>present</em> 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> T processWithGetSemantics(AnnotatedElement element, String annotationType,
|
||||||
|
Processor<T> processor, Set<AnnotatedElement> 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> T processWithFindSemantics(AnnotatedElement element, String annotationType,
|
||||||
|
boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces,
|
||||||
|
boolean searchOnMethodsInSuperclasses, Processor<T> processor) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return doProcess(element, annotationType, searchInterfaces, searchClassHierarchy, processor,
|
return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses,
|
||||||
new HashSet<AnnotatedElement>(), 0);
|
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet<AnnotatedElement>(), 0);
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
throw new IllegalStateException("Failed to introspect annotations: " + element, ex);
|
throw new IllegalStateException("Failed to introspect annotations: " + element, ex);
|
||||||
|
@ -228,24 +342,29 @@ public class AnnotatedElementUtils {
|
||||||
*
|
*
|
||||||
* @param element the annotated element
|
* @param element the annotated element
|
||||||
* @param annotationType the annotation type to find
|
* @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
|
* annotated element is a class
|
||||||
* @param searchClassHierarchy whether or not to search the class hierarchy
|
* @param searchOnSuperclasses whether to search on superclasses, if
|
||||||
* recursively, if the annotated element is a class
|
* 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 processor the processor to delegate to
|
||||||
* @param visited the set of annotated elements that have already been visited
|
* @param visited the set of annotated elements that have already been visited
|
||||||
* @param metaDepth the depth of the annotation relative to the initial element
|
* @param metaDepth the depth of the annotation relative to the initial element
|
||||||
* @return the result of the processor
|
* @return the result of the processor
|
||||||
*/
|
*/
|
||||||
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean searchInterfaces,
|
private static <T> T processWithFindSemantics(AnnotatedElement element, String annotationType,
|
||||||
boolean searchClassHierarchy, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
|
boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces,
|
||||||
|
boolean searchOnMethodsInSuperclasses, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
|
||||||
|
|
||||||
if (visited.add(element)) {
|
if (visited.add(element)) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Local annotations: declared or (declared + inherited).
|
// Local annotations: declared or (declared + inherited).
|
||||||
Annotation[] annotations =
|
Annotation[] annotations = (searchOnSuperclasses ? element.getDeclaredAnnotations()
|
||||||
(searchClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
|
: element.getAnnotations());
|
||||||
|
|
||||||
// Search in local annotations
|
// Search in local annotations
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
|
@ -254,8 +373,9 @@ public class AnnotatedElementUtils {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
|
result = processWithFindSemantics(annotation.annotationType(), annotationType,
|
||||||
searchClassHierarchy, processor, visited, metaDepth + 1);
|
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||||
|
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processor.postProcess(annotation, result);
|
processor.postProcess(annotation, result);
|
||||||
return result;
|
return result;
|
||||||
|
@ -266,8 +386,9 @@ public class AnnotatedElementUtils {
|
||||||
// Search in meta annotations on local annotations
|
// Search in meta annotations on local annotations
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
||||||
T result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
|
T result = processWithFindSemantics(annotation.annotationType(), annotationType,
|
||||||
searchClassHierarchy, processor, visited, metaDepth);
|
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||||
|
searchOnMethodsInSuperclasses, processor, visited, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processor.postProcess(annotation, result);
|
processor.postProcess(annotation, result);
|
||||||
return result;
|
return result;
|
||||||
|
@ -275,26 +396,93 @@ public class AnnotatedElementUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search on interfaces
|
if (element instanceof Method) {
|
||||||
if (searchInterfaces && element instanceof Class) {
|
Method method = (Method) element;
|
||||||
Class<?> clazz = (Class<?>) element;
|
|
||||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
// Search on possibly bridged method
|
||||||
T result = doProcess(ifc, annotationType, searchInterfaces, searchClassHierarchy, processor,
|
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||||
visited, metaDepth);
|
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) {
|
if (result != null) {
|
||||||
return result;
|
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 (element instanceof Class) {
|
||||||
if (searchClassHierarchy && element instanceof Class) {
|
Class<?> clazz = (Class<?>) element;
|
||||||
Class<?> superclass = ((Class<?>) element).getSuperclass();
|
|
||||||
if (superclass != null && !superclass.equals(Object.class)) {
|
// Search on interfaces
|
||||||
T result = doProcess(superclass, annotationType, searchInterfaces, searchClassHierarchy,
|
if (searchOnInterfaces) {
|
||||||
processor, visited, metaDepth);
|
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||||
if (result != null) {
|
T result = processWithFindSemantics(ifc, annotationType, searchOnInterfaces,
|
||||||
return result;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> T searchOnInterfaces(Method method, String annotationType, boolean searchOnInterfaces,
|
||||||
|
boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses,
|
||||||
|
Processor<T> processor, Set<AnnotatedElement> 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 <T> the result type
|
* @param <T> the result type
|
||||||
*/
|
*/
|
||||||
private interface Processor<T> {
|
private static interface Processor<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to process the annotation.
|
* Process the actual target annotation once it has been found by
|
||||||
|
* the search algorithm.
|
||||||
|
*
|
||||||
* <p>The {@code metaDepth} parameter represents the depth of the
|
* <p>The {@code metaDepth} parameter represents the depth of the
|
||||||
* annotation relative to the initial element. For example, an annotation
|
* annotation relative to the initial element. For example, an annotation
|
||||||
* that is <em>present</em> on the element will have a depth of 0; a
|
* that is <em>present</em> on the element will have a depth of 0; a
|
||||||
* meta-annotation will have a depth of 1; and a meta-meta-annotation
|
* meta-annotation will have a depth of 1; and a meta-meta-annotation
|
||||||
* will have a depth of 2.
|
* will have a depth of 2.
|
||||||
|
*
|
||||||
* @param annotation the annotation to process
|
* @param annotation the annotation to process
|
||||||
* @param metaDepth the depth of the annotation relative to the initial element
|
* @param metaDepth the depth of the annotation relative to the initial element
|
||||||
* @return the result of the processing, or {@code null} to continue
|
* @return the result of the processing, or {@code null} to continue
|
||||||
*/
|
*/
|
||||||
T process(Annotation annotation, int metaDepth);
|
T process(Annotation annotation, int metaDepth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-process the result returned by the {@link #process} method.
|
||||||
|
*
|
||||||
|
* <p>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);
|
void postProcess(Annotation annotation, T result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class MergeAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,9 +100,9 @@ public abstract class AnnotationUtils {
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) {
|
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
|
||||||
if (annotationType.isInstance(ann)) {
|
if (annotationType.isInstance(ann)) {
|
||||||
return (T) ann;
|
return (A) ann;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return ann.annotationType().getAnnotation(annotationType);
|
return ann.annotationType().getAnnotation(annotationType);
|
||||||
|
@ -126,9 +126,9 @@ public abstract class AnnotationUtils {
|
||||||
* @return the matching annotation, or {@code null} if not found
|
* @return the matching annotation, or {@code null} if not found
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
*/
|
*/
|
||||||
public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) {
|
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||||
try {
|
try {
|
||||||
T ann = annotatedElement.getAnnotation(annotationType);
|
A ann = annotatedElement.getAnnotation(annotationType);
|
||||||
if (ann == null) {
|
if (ann == null) {
|
||||||
for (Annotation metaAnn : annotatedElement.getAnnotations()) {
|
for (Annotation metaAnn : annotatedElement.getAnnotations()) {
|
||||||
ann = metaAnn.annotationType().getAnnotation(annotationType);
|
ann = metaAnn.annotationType().getAnnotation(annotationType);
|
||||||
|
@ -294,18 +294,18 @@ public abstract class AnnotationUtils {
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static <T extends Annotation> T findAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType, Set<Annotation> visited) {
|
private static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
|
||||||
Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
|
Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
|
||||||
try {
|
try {
|
||||||
Annotation[] anns = annotatedElement.getDeclaredAnnotations();
|
Annotation[] anns = annotatedElement.getDeclaredAnnotations();
|
||||||
for (Annotation ann : anns) {
|
for (Annotation ann : anns) {
|
||||||
if (ann.annotationType().equals(annotationType)) {
|
if (ann.annotationType().equals(annotationType)) {
|
||||||
return (T) ann;
|
return (A) ann;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Annotation ann : anns) {
|
for (Annotation ann : anns) {
|
||||||
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
|
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) {
|
if (annotation != null) {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
@ -392,16 +392,16 @@ public abstract class AnnotationUtils {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
|
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
|
||||||
Boolean flag = annotatedInterfaceCache.get(iface);
|
Boolean flag = annotatedInterfaceCache.get(iface);
|
||||||
if (flag != null) {
|
if (flag != null) {
|
||||||
return flag;
|
return flag.booleanValue();
|
||||||
}
|
}
|
||||||
boolean found = false;
|
Boolean found = Boolean.FALSE;
|
||||||
for (Method ifcMethod : iface.getMethods()) {
|
for (Method ifcMethod : iface.getMethods()) {
|
||||||
try {
|
try {
|
||||||
if (ifcMethod.getAnnotations().length > 0) {
|
if (ifcMethod.getAnnotations().length > 0) {
|
||||||
found = true;
|
found = Boolean.TRUE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,7 +411,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotatedInterfaceCache.put(iface, found);
|
annotatedInterfaceCache.put(iface, found);
|
||||||
return found;
|
return found.booleanValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -126,54 +126,6 @@ public class AnnotatedElementUtilsTests {
|
||||||
attributes.getBoolean("readOnly"));
|
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.
|
// TODO [SPR-11598] Enable test.
|
||||||
@Ignore("Disabled until SPR-11598 is resolved")
|
@Ignore("Disabled until SPR-11598 is resolved")
|
||||||
@Test
|
@Test
|
||||||
|
@ -183,37 +135,126 @@ public class AnnotatedElementUtilsTests {
|
||||||
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes);
|
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [SPR-12738] Enable test.
|
/** @since 4.2 */
|
||||||
@Ignore("Disabled until SPR-12738 is resolved")
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException {
|
public void getAnnotationAttributesOnInheritedAnnotationInterface() {
|
||||||
String name = Order.class.getName();
|
String name = Transactional.class.getName();
|
||||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface");
|
AnnotationAttributes attributes = getAnnotationAttributes(InheritedAnnotationInterface.class, name);
|
||||||
AnnotationAttributes attributes = getAnnotationAttributes(method, name);
|
assertNotNull("Should get @Transactional on InheritedAnnotationInterface", attributes);
|
||||||
assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method",
|
|
||||||
attributes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [SPR-12738] Enable test.
|
/** @since 4.2 */
|
||||||
@Ignore("Disabled until SPR-12738 is resolved")
|
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException {
|
public void findAnnotationAttributesOnInheritedAnnotationInterface() {
|
||||||
String name = Transactional.class.getName();
|
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");
|
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);
|
assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation.handle() method", attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [SPR-12738] Enable test.
|
/**
|
||||||
|
* TODO [SPR-12738] Enable test.
|
||||||
|
*
|
||||||
|
* <p>{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas,
|
||||||
|
* {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}.
|
||||||
|
*
|
||||||
|
* <p>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")
|
@Ignore("Disabled until SPR-12738 is resolved")
|
||||||
@Test
|
@Test
|
||||||
public void getAnnotationAttributesInheritedFromParameterizedMethod() throws NoSuchMethodException {
|
public void findAnnotationAttributesInheritedFromBridgedMethod() throws NoSuchMethodException {
|
||||||
String name = Transactional.class.getName();
|
|
||||||
Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleParameterized", String.class);
|
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);
|
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> {
|
||||||
|
|
||||||
|
T getFor(Class<T> cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class StringGenericParameter implements GenericParameter<String> {
|
||||||
|
|
||||||
|
@Order
|
||||||
|
@Override
|
||||||
|
public String getFor(Class<String> cls) {
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFor(Integer integer) {
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public static interface InheritedAnnotationInterface {
|
public static interface InheritedAnnotationInterface {
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,8 +286,8 @@ public abstract class MetaAnnotationUtils {
|
||||||
this.declaringClass = declaringClass;
|
this.declaringClass = declaringClass;
|
||||||
this.composedAnnotation = composedAnnotation;
|
this.composedAnnotation = composedAnnotation;
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
this.annotationAttributes = AnnotatedElementUtils.getAnnotationAttributes(rootDeclaringClass,
|
this.annotationAttributes = AnnotatedElementUtils.findAnnotationAttributes(
|
||||||
annotation.annotationType().getName(), true, true, false, false);
|
rootDeclaringClass, annotation.annotationType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<?> getRootDeclaringClass() {
|
public Class<?> getRootDeclaringClass() {
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* Strategy implementation for parsing JTA 1.2's {@link javax.transaction.Transactional} annotation.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
|
@ -40,8 +39,7 @@ public class JtaTransactionAnnotationParser implements TransactionAnnotationPars
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
||||||
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae,
|
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, javax.transaction.Transactional.class.getName());
|
||||||
javax.transaction.Transactional.class.getName(), false, false, false, false);
|
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
return parseTransactionAnnotation(ann);
|
return parseTransactionAnnotation(ann);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* Strategy implementation for parsing Spring's {@link Transactional} annotation.
|
||||||
*
|
*
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Sam Brannen
|
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
|
@ -40,8 +39,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
|
||||||
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName(),
|
AnnotationAttributes ann = AnnotatedElementUtils.getAnnotationAttributes(ae, Transactional.class.getName());
|
||||||
false, false, false, false);
|
|
||||||
if (ann != null) {
|
if (ann != null) {
|
||||||
return parseTransactionAnnotation(ann);
|
return parseTransactionAnnotation(ann);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
@ -150,16 +149,13 @@ public class TransactionalEventListenerTests {
|
||||||
getEventCollector().assertTotalEventsCount(1); // After rollback not invoked
|
getEventCollector().assertTotalEventsCount(1); // After rollback not invoked
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [SPR-12738] Enable test.
|
|
||||||
@Ignore("Disabled until SPR-12738 is resolved")
|
|
||||||
@Test
|
@Test
|
||||||
public void afterCommitWithTransactionalComponentListenerProxiedViaDynamicProxy() {
|
public void afterCommitWithTransactionalComponentListenerProxiedViaDynamicProxy() {
|
||||||
load(TransactionalConfiguration.class, TransactionalComponentAfterCommitTestListener.class);
|
load(TransactionalConfiguration.class, TransactionalComponentTestListener.class);
|
||||||
this.transactionTemplate.execute(status -> {
|
this.transactionTemplate.execute(status -> {
|
||||||
getContext().publishEvent("SKIP");
|
getContext().publishEvent("SKIP");
|
||||||
getEventCollector().assertNoEventReceived();
|
getEventCollector().assertNoEventReceived();
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
});
|
});
|
||||||
getEventCollector().assertNoEventReceived();
|
getEventCollector().assertNoEventReceived();
|
||||||
}
|
}
|
||||||
|
@ -280,7 +276,6 @@ public class TransactionalEventListenerTests {
|
||||||
getContext().publishEvent("SKIP");
|
getContext().publishEvent("SKIP");
|
||||||
getEventCollector().assertNoEventReceived();
|
getEventCollector().assertNoEventReceived();
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
});
|
});
|
||||||
getEventCollector().assertNoEventReceived();
|
getEventCollector().assertNoEventReceived();
|
||||||
}
|
}
|
||||||
|
@ -460,14 +455,15 @@ public class TransactionalEventListenerTests {
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Component
|
@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);
|
void handleAfterCommit(String data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TransactionalComponentAfterCommitTestListener extends BaseTransactionalTestListener implements
|
static class TransactionalComponentTestListener extends BaseTransactionalTestListener implements
|
||||||
TransactionalComponentAfterCommitTestListenerInterface {
|
TransactionalComponentTestListenerInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleAfterCommit(String data) {
|
public void handleAfterCommit(String data) {
|
||||||
|
|
Loading…
Reference in New Issue