Comprehensively cache annotated methods for interfaces and superclasses
Issue: SPR-16675
This commit is contained in:
@@ -1050,17 +1050,48 @@ public class AnnotatedElementUtils {
|
|||||||
try {
|
try {
|
||||||
// Locally declared annotations (ignoring @Inherited)
|
// Locally declared annotations (ignoring @Inherited)
|
||||||
Annotation[] annotations = element.getDeclaredAnnotations();
|
Annotation[] annotations = element.getDeclaredAnnotations();
|
||||||
List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null);
|
if (annotations.length > 0) {
|
||||||
|
List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null);
|
||||||
|
|
||||||
// Search in local annotations
|
// Search in local annotations
|
||||||
for (Annotation annotation : annotations) {
|
for (Annotation annotation : annotations) {
|
||||||
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
|
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) {
|
||||||
if (currentAnnotationType == annotationType ||
|
if (currentAnnotationType == annotationType ||
|
||||||
currentAnnotationType.getName().equals(annotationName) ||
|
currentAnnotationType.getName().equals(annotationName) ||
|
||||||
processor.alwaysProcesses()) {
|
processor.alwaysProcesses()) {
|
||||||
T result = processor.process(element, annotation, metaDepth);
|
T result = processor.process(element, annotation, metaDepth);
|
||||||
|
if (result != null) {
|
||||||
|
if (aggregatedResults != null && metaDepth == 0) {
|
||||||
|
aggregatedResults.add(result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Repeatable annotations in container?
|
||||||
|
else if (currentAnnotationType == containerType) {
|
||||||
|
for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
|
||||||
|
T result = processor.process(element, contained, metaDepth);
|
||||||
|
if (aggregatedResults != null && result != null) {
|
||||||
|
// No need to post-process since repeatable annotations within a
|
||||||
|
// container cannot be composed annotations.
|
||||||
|
aggregatedResults.add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively search in meta-annotations
|
||||||
|
for (Annotation annotation : annotations) {
|
||||||
|
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
|
||||||
|
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
|
||||||
|
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
|
||||||
|
containerType, processor, visited, metaDepth + 1);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
processor.postProcess(currentAnnotationType, annotation, result);
|
||||||
if (aggregatedResults != null && metaDepth == 0) {
|
if (aggregatedResults != null && metaDepth == 0) {
|
||||||
aggregatedResults.add(result);
|
aggregatedResults.add(result);
|
||||||
}
|
}
|
||||||
@@ -1069,43 +1100,14 @@ public class AnnotatedElementUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Repeatable annotations in container?
|
|
||||||
else if (currentAnnotationType == containerType) {
|
|
||||||
for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
|
|
||||||
T result = processor.process(element, contained, metaDepth);
|
|
||||||
if (aggregatedResults != null && result != null) {
|
|
||||||
// No need to post-process since repeatable annotations within a
|
|
||||||
// container cannot be composed annotations.
|
|
||||||
aggregatedResults.add(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively search in meta-annotations
|
if (!CollectionUtils.isEmpty(aggregatedResults)) {
|
||||||
for (Annotation annotation : annotations) {
|
// Prepend to support top-down ordering within class hierarchies
|
||||||
Class<? extends Annotation> currentAnnotationType = annotation.annotationType();
|
processor.getAggregatedResults().addAll(0, aggregatedResults);
|
||||||
if (hasSearchableMetaAnnotations(currentAnnotationType, annotationType, annotationName)) {
|
|
||||||
T result = searchWithFindSemantics(currentAnnotationType, annotationType, annotationName,
|
|
||||||
containerType, processor, visited, metaDepth + 1);
|
|
||||||
if (result != null) {
|
|
||||||
processor.postProcess(currentAnnotationType, annotation, result);
|
|
||||||
if (aggregatedResults != null && metaDepth == 0) {
|
|
||||||
aggregatedResults.add(result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(aggregatedResults)) {
|
|
||||||
// Prepend to support top-down ordering within class hierarchies
|
|
||||||
processor.getAggregatedResults().addAll(0, aggregatedResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element instanceof Method) {
|
if (element instanceof Method) {
|
||||||
Method method = (Method) element;
|
Method method = (Method) element;
|
||||||
T result;
|
T result;
|
||||||
@@ -1123,8 +1125,8 @@ public class AnnotatedElementUtils {
|
|||||||
// Search on methods in interfaces declared locally
|
// Search on methods in interfaces declared locally
|
||||||
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
|
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
|
||||||
if (ifcs.length > 0) {
|
if (ifcs.length > 0) {
|
||||||
result = searchOnInterfaces(method, annotationType, annotationName, containerType,
|
result = searchOnInterfaces(method, annotationType, annotationName,
|
||||||
processor, visited, metaDepth, ifcs);
|
containerType, processor, visited, metaDepth, ifcs);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1137,23 +1139,23 @@ public class AnnotatedElementUtils {
|
|||||||
if (clazz == null || Object.class == clazz) {
|
if (clazz == null || Object.class == clazz) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(clazz);
|
||||||
try {
|
if (!annotatedMethods.isEmpty()) {
|
||||||
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
|
for (Method annotatedMethod : annotatedMethods) {
|
||||||
Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
|
if (annotatedMethod.getName().equals(method.getName()) &&
|
||||||
result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, annotationName,
|
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
|
||||||
containerType, processor, visited, metaDepth);
|
Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod);
|
||||||
if (result != null) {
|
result = searchWithFindSemantics(resolvedSuperMethod, annotationType, annotationName,
|
||||||
return result;
|
containerType, processor, visited, metaDepth);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException ex) {
|
|
||||||
// No equivalent method found
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search on interfaces declared on superclass
|
// Search on interfaces declared on superclass
|
||||||
result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
|
result = searchOnInterfaces(method, annotationType, annotationName,
|
||||||
visited, metaDepth, clazz.getInterfaces());
|
containerType, processor, visited, metaDepth, clazz.getInterfaces());
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1164,8 +1166,8 @@ public class AnnotatedElementUtils {
|
|||||||
|
|
||||||
// Search on interfaces
|
// Search on interfaces
|
||||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||||
T result = searchWithFindSemantics(ifc, annotationType, annotationName, containerType,
|
T result = searchWithFindSemantics(ifc, annotationType, annotationName,
|
||||||
processor, visited, metaDepth);
|
containerType, processor, visited, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1174,8 +1176,8 @@ public class AnnotatedElementUtils {
|
|||||||
// Search on superclass
|
// Search on superclass
|
||||||
Class<?> superclass = clazz.getSuperclass();
|
Class<?> superclass = clazz.getSuperclass();
|
||||||
if (superclass != null && Object.class != superclass) {
|
if (superclass != null && Object.class != superclass) {
|
||||||
T result = searchWithFindSemantics(superclass, annotationType, annotationName, containerType,
|
T result = searchWithFindSemantics(superclass, annotationType, annotationName,
|
||||||
processor, visited, metaDepth);
|
containerType, processor, visited, metaDepth);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1195,18 +1197,18 @@ public class AnnotatedElementUtils {
|
|||||||
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
|
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
|
||||||
|
|
||||||
for (Class<?> ifc : ifcs) {
|
for (Class<?> ifc : ifcs) {
|
||||||
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(ifc)) {
|
Set<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(ifc);
|
||||||
try {
|
if (!annotatedMethods.isEmpty()) {
|
||||||
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
|
for (Method annotatedMethod : annotatedMethods) {
|
||||||
T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
|
if (annotatedMethod.getName().equals(method.getName()) &&
|
||||||
processor, visited, metaDepth);
|
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
|
||||||
if (result != null) {
|
T result = searchWithFindSemantics(annotatedMethod, annotationType, annotationName,
|
||||||
return result;
|
containerType, processor, visited, metaDepth);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NoSuchMethodException ex) {
|
|
||||||
// Skip this interface - it doesn't have the method...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -122,7 +123,7 @@ public abstract class AnnotationUtils {
|
|||||||
private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
|
private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
|
||||||
new ConcurrentReferenceHashMap<>(256);
|
new ConcurrentReferenceHashMap<>(256);
|
||||||
|
|
||||||
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
|
private static final Map<Class<?>, Set<Method>> annotatedBaseTypeCache =
|
||||||
new ConcurrentReferenceHashMap<>(256);
|
new ConcurrentReferenceHashMap<>(256);
|
||||||
|
|
||||||
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
|
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
|
||||||
@@ -539,7 +540,6 @@ public abstract class AnnotationUtils {
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||||
result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);
|
result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
|
result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
|
||||||
}
|
}
|
||||||
@@ -574,48 +574,63 @@ public abstract class AnnotationUtils {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
|
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
|
||||||
A annotation = null;
|
|
||||||
for (Class<?> ifc : ifcs) {
|
for (Class<?> ifc : ifcs) {
|
||||||
if (isInterfaceWithAnnotatedMethods(ifc)) {
|
Set<Method> annotatedMethods = getAnnotatedMethodsInBaseType(ifc);
|
||||||
try {
|
if (!annotatedMethods.isEmpty()) {
|
||||||
Method equivalentMethod = ifc.getMethod(method.getName(), method.getParameterTypes());
|
for (Method annotatedMethod : annotatedMethods) {
|
||||||
annotation = getAnnotation(equivalentMethod, annotationType);
|
if (annotatedMethod.getName().equals(method.getName()) &&
|
||||||
}
|
Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())) {
|
||||||
catch (NoSuchMethodException ex) {
|
A annotation = getAnnotation(annotatedMethod, annotationType);
|
||||||
// Skip this interface - it doesn't have the method...
|
if (annotation != null) {
|
||||||
}
|
return annotation;
|
||||||
if (annotation != null) {
|
}
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return annotation;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isInterfaceWithAnnotatedMethods(Class<?> ifc) {
|
static Set<Method> getAnnotatedMethodsInBaseType(Class<?> baseType) {
|
||||||
if (ClassUtils.isJavaLanguageInterface(ifc)) {
|
if (ClassUtils.isJavaLanguageInterface(baseType)) {
|
||||||
return false;
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Boolean found = annotatedInterfaceCache.get(ifc);
|
Set<Method> annotatedMethods = annotatedBaseTypeCache.get(baseType);
|
||||||
if (found != null) {
|
if (annotatedMethods != null) {
|
||||||
return found;
|
return annotatedMethods;
|
||||||
}
|
}
|
||||||
found = Boolean.FALSE;
|
Method[] methods = (baseType.isInterface() ? baseType.getMethods() : baseType.getDeclaredMethods());
|
||||||
for (Method ifcMethod : ifc.getMethods()) {
|
for (Method baseMethod : methods) {
|
||||||
try {
|
try {
|
||||||
Annotation[] anns = ifcMethod.getAnnotations();
|
if (hasSearchableAnnotations(baseMethod)) {
|
||||||
if (anns.length > 1 || (anns.length == 1 && anns[0].annotationType() != Nullable.class)) {
|
if (annotatedMethods == null) {
|
||||||
found = Boolean.TRUE;
|
annotatedMethods = new HashSet<>();
|
||||||
break;
|
}
|
||||||
|
annotatedMethods.add(baseMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
handleIntrospectionFailure(ifcMethod, ex);
|
handleIntrospectionFailure(baseMethod, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotatedInterfaceCache.put(ifc, found);
|
if (annotatedMethods == null) {
|
||||||
return found;
|
annotatedMethods = Collections.emptySet();
|
||||||
|
}
|
||||||
|
annotatedBaseTypeCache.put(baseType, annotatedMethods);
|
||||||
|
return annotatedMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasSearchableAnnotations(Method ifcMethod) {
|
||||||
|
Annotation[] anns = ifcMethod.getAnnotations();
|
||||||
|
if (anns.length == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (anns.length == 1) {
|
||||||
|
Class<?> annType = anns[0].annotationType();
|
||||||
|
return (annType != Nullable.class && annType != Deprecated.class);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1875,6 +1890,20 @@ public abstract class AnnotationUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the internal annotation metadata cache.
|
||||||
|
* @since 4.3.15
|
||||||
|
*/
|
||||||
|
public static void clearCache() {
|
||||||
|
findAnnotationCache.clear();
|
||||||
|
metaPresentCache.clear();
|
||||||
|
annotatedBaseTypeCache.clear();
|
||||||
|
synthesizableCache.clear();
|
||||||
|
attributeAliasesCache.clear();
|
||||||
|
attributeMethodsCache.clear();
|
||||||
|
aliasDescriptorCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache key for the AnnotatedElement cache.
|
* Cache key for the AnnotatedElement cache.
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import java.lang.annotation.Repeatable;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,7 +38,6 @@ import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
|||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ReflectionUtils;
|
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
import static java.util.Arrays.*;
|
||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
@@ -64,23 +62,8 @@ public class AnnotationUtilsTests {
|
|||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void clearCachesBeforeTests() {
|
public void clearCacheBeforeTests() {
|
||||||
clearCaches();
|
AnnotationUtils.clearCache();
|
||||||
}
|
|
||||||
|
|
||||||
static void clearCaches() {
|
|
||||||
clearCache("findAnnotationCache", "annotatedInterfaceCache", "metaPresentCache", "synthesizableCache",
|
|
||||||
"attributeAliasesCache", "attributeMethodsCache", "aliasDescriptorCache");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clearCache(String... cacheNames) {
|
|
||||||
stream(cacheNames).forEach(cacheName -> getCache(cacheName).clear());
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<?, ?> getCache(String cacheName) {
|
|
||||||
Field field = ReflectionUtils.findField(AnnotationUtils.class, cacheName);
|
|
||||||
ReflectionUtils.makeAccessible(field);
|
|
||||||
return (Map<?, ?>) ReflectionUtils.getField(field, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1544,9 +1527,9 @@ public class AnnotationUtilsTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void interfaceWithAnnotatedMethods() {
|
public void interfaceWithAnnotatedMethods() {
|
||||||
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NonAnnotatedInterface.class));
|
assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NonAnnotatedInterface.class).isEmpty());
|
||||||
assertTrue(AnnotationUtils.isInterfaceWithAnnotatedMethods(AnnotatedInterface.class));
|
assertFalse(AnnotationUtils.getAnnotatedMethodsInBaseType(AnnotatedInterface.class).isEmpty());
|
||||||
assertFalse(AnnotationUtils.isInterfaceWithAnnotatedMethods(NullableAnnotatedInterface.class));
|
assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NullableAnnotatedInterface.class).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 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.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -41,13 +42,20 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.*;
|
|||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
||||||
|
|
||||||
@Before
|
@Override
|
||||||
public void clearCachesBeforeTests() {
|
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
||||||
AnnotationUtilsTests.clearCaches();
|
Map<String, Object> attributes = Collections.singletonMap(expected, expected);
|
||||||
|
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void clearCacheBeforeTests() {
|
||||||
|
AnnotationUtils.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() {
|
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() throws Exception {
|
||||||
Map<String, Object> attributes = new HashMap<>();
|
Map<String, Object> attributes = new HashMap<>();
|
||||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
|
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
|
||||||
put("groovyScript", "");
|
put("groovyScript", "");
|
||||||
@@ -64,7 +72,7 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enrichAndValidateAttributesWithImplicitAliases() {
|
public void enrichAndValidateAttributesWithImplicitAliases() throws Exception {
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
||||||
put("groovyScript", "groovy!");
|
put("groovyScript", "groovy!");
|
||||||
}};
|
}};
|
||||||
@@ -85,7 +93,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enrichAndValidateAttributesWithSingleElementThatOverridesAnArray() {
|
public void enrichAndValidateAttributesWithSingleElementThatOverridesAnArray() {
|
||||||
// @formatter:off
|
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
||||||
// Intentionally storing 'value' as a single String instead of an array.
|
// Intentionally storing 'value' as a single String instead of an array.
|
||||||
// put("value", asArray("/foo"));
|
// put("value", asArray("/foo"));
|
||||||
@@ -99,7 +106,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
|||||||
put("name", "test");
|
put("name", "test");
|
||||||
put("method", new RequestMethod[0]);
|
put("method", new RequestMethod[0]);
|
||||||
}};
|
}};
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(attributes, WebMapping.class, null);
|
MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(attributes, WebMapping.class, null);
|
||||||
Map<String, Object> enriched = extractor.getSource();
|
Map<String, Object> enriched = extractor.getSource();
|
||||||
@@ -109,18 +115,17 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) {
|
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) throws Exception {
|
||||||
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class;
|
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class;
|
||||||
|
|
||||||
// Since the ordering of attribute methods returned by the JVM is
|
// Since the ordering of attribute methods returned by the JVM is non-deterministic,
|
||||||
// non-deterministic, we have to rig the attributeAliasesCache in AnnotationUtils
|
// we have to rig the attributeAliasesCache in AnnotationUtils so that the tests
|
||||||
// so that the tests consistently fail in case enrichAndValidateAttributes() is
|
// consistently fail in case enrichAndValidateAttributes() is buggy.
|
||||||
// buggy.
|
// Otherwise, these tests would intermittently pass even for an invalid implementation.
|
||||||
//
|
Field cacheField = AnnotationUtils.class.getDeclaredField("attributeAliasesCache");
|
||||||
// Otherwise, these tests would intermittently pass even for an invalid
|
cacheField.setAccessible(true);
|
||||||
// implementation.
|
|
||||||
Map<Class<? extends Annotation>, MultiValueMap<String, String>> attributeAliasesCache =
|
Map<Class<? extends Annotation>, MultiValueMap<String, String>> attributeAliasesCache =
|
||||||
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) AnnotationUtilsTests.getCache("attributeAliasesCache");
|
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) cacheField.get(null);
|
||||||
|
|
||||||
// Declare aliases in an order that will cause enrichAndValidateAttributes() to
|
// Declare aliases in an order that will cause enrichAndValidateAttributes() to
|
||||||
// fail unless it considers all aliases in the set of implicit aliases.
|
// fail unless it considers all aliases in the set of implicit aliases.
|
||||||
@@ -141,10 +146,4 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
|||||||
expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue)));
|
expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
|
||||||
Map<String, Object> attributes = Collections.singletonMap(expected, expected);
|
|
||||||
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user