ReflectionUtils caches Class.getDeclaredMethods() results; AnnotationUtils caches findAnnotation results
Issue: SPR-11882
This commit is contained in:
parent
b0979cbab6
commit
682a910bb6
|
|
@ -26,13 +26,13 @@ import java.util.LinkedHashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
|
|
@ -68,7 +68,11 @@ public abstract class AnnotationUtils {
|
|||
|
||||
private static final Log logger = LogFactory.getLog(AnnotationUtils.class);
|
||||
|
||||
private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>();
|
||||
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
||||
|
||||
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
|
||||
new ConcurrentReferenceHashMap<Class<?>, Boolean>(256);
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -222,32 +226,40 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the annotation type to look for
|
||||
* @return the annotation found, or {@code null} if none
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
|
||||
A annotation = getAnnotation(method, annotationType);
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
if (annotation == null) {
|
||||
annotation = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
|
||||
}
|
||||
while (annotation == null) {
|
||||
clazz = clazz.getSuperclass();
|
||||
if (clazz == null || clazz.equals(Object.class)) {
|
||||
break;
|
||||
AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType);
|
||||
A result = (A) findAnnotationCache.get(cacheKey);
|
||||
if (result == null) {
|
||||
result = getAnnotation(method, annotationType);
|
||||
Class<?> clazz = method.getDeclaringClass();
|
||||
if (result == null) {
|
||||
result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
|
||||
}
|
||||
try {
|
||||
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
|
||||
annotation = getAnnotation(equivalentMethod, annotationType);
|
||||
while (result == null) {
|
||||
clazz = clazz.getSuperclass();
|
||||
if (clazz == null || clazz.equals(Object.class)) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
|
||||
result = getAnnotation(equivalentMethod, annotationType);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// No equivalent method found
|
||||
}
|
||||
if (result == null) {
|
||||
result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
// No equivalent method found
|
||||
}
|
||||
if (annotation == null) {
|
||||
annotation = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
|
||||
if (result != null) {
|
||||
findAnnotationCache.put(cacheKey, result);
|
||||
}
|
||||
}
|
||||
return annotation;
|
||||
return result;
|
||||
}
|
||||
|
||||
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<?> iface : ifcs) {
|
||||
if (isInterfaceWithAnnotatedMethods(iface)) {
|
||||
|
|
@ -267,30 +279,28 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
|
||||
synchronized (annotatedInterfaceCache) {
|
||||
Boolean flag = annotatedInterfaceCache.get(iface);
|
||||
if (flag != null) {
|
||||
return flag;
|
||||
}
|
||||
boolean found = false;
|
||||
for (Method ifcMethod : iface.getMethods()) {
|
||||
try {
|
||||
if (ifcMethod.getAnnotations().length > 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
// We're probably hitting a non-present optional arrangement - let's back out.
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Failed to introspect annotations on [" + ifcMethod + "]: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
annotatedInterfaceCache.put(iface, found);
|
||||
return found;
|
||||
Boolean flag = annotatedInterfaceCache.get(iface);
|
||||
if (flag != null) {
|
||||
return flag;
|
||||
}
|
||||
boolean found = false;
|
||||
for (Method ifcMethod : iface.getMethods()) {
|
||||
try {
|
||||
if (ifcMethod.getAnnotations().length > 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
// We're probably hitting a non-present optional arrangement - let's back out.
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Failed to introspect annotations on [" + ifcMethod + "]: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
annotatedInterfaceCache.put(iface, found);
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -315,8 +325,17 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the type of annotation to look for
|
||||
* @return the annotation if found, or {@code null} if not found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
|
||||
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
|
||||
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
|
||||
A result = (A) findAnnotationCache.get(cacheKey);
|
||||
if (result == null) {
|
||||
result = findAnnotation(clazz, annotationType, new HashSet<Annotation>());
|
||||
if (result != null) {
|
||||
findAnnotationCache.put(cacheKey, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -676,6 +695,40 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default cache key for the TransactionAttribute cache.
|
||||
*/
|
||||
private static class AnnotationCacheKey {
|
||||
|
||||
private final AnnotatedElement element;
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
public AnnotationCacheKey(AnnotatedElement element, Class<? extends Annotation> annotationType) {
|
||||
this.element = element;
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AnnotationCacheKey)) {
|
||||
return false;
|
||||
}
|
||||
AnnotationCacheKey otherKey = (AnnotationCacheKey) other;
|
||||
return (this.element.equals(otherKey.element) &&
|
||||
ObjectUtils.nullSafeEquals(this.annotationType, otherKey.annotationType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.element.hashCode() * 29 + this.annotationType.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AnnotationCollector<A extends Annotation> {
|
||||
|
||||
private final Class<? extends Annotation> containerAnnotationType;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.sql.SQLException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
|
@ -56,6 +57,12 @@ public abstract class ReflectionUtils {
|
|||
*/
|
||||
private static final Pattern CGLIB_RENAMED_METHOD_PATTERN = Pattern.compile("(.+)\\$\\d+");
|
||||
|
||||
/**
|
||||
* Cache for {@link Class#getDeclaredMethods()}, allowing for fast resolution.
|
||||
*/
|
||||
private static final Map<Class<?>, Method[]> declaredMethodsCache =
|
||||
new ConcurrentReferenceHashMap<Class<?>, Method[]>(256);
|
||||
|
||||
|
||||
/**
|
||||
* Attempt to find a {@link Field field} on the supplied {@link Class} with the
|
||||
|
|
@ -162,7 +169,7 @@ public abstract class ReflectionUtils {
|
|||
Assert.notNull(name, "Method name must not be null");
|
||||
Class<?> searchType = clazz;
|
||||
while (searchType != null) {
|
||||
Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
|
||||
Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
|
||||
for (Method method : methods) {
|
||||
if (name.equals(method.getName()) &&
|
||||
(paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
|
||||
|
|
@ -479,7 +486,7 @@ public abstract class ReflectionUtils {
|
|||
throws IllegalArgumentException {
|
||||
|
||||
// Keep backing up the inheritance hierarchy.
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
Method[] methods = getDeclaredMethods(clazz);
|
||||
for (Method method : methods) {
|
||||
if (mf != null && !mf.matches(method)) {
|
||||
continue;
|
||||
|
|
@ -554,6 +561,19 @@ public abstract class ReflectionUtils {
|
|||
return methods.toArray(new Method[methods.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves {@link Class#getDeclaredMethods()} from a local cache
|
||||
* in order to avoid the JVM's SecurityManager check and defensive array copying.
|
||||
*/
|
||||
private static Method[] getDeclaredMethods(Class<?> clazz) {
|
||||
Method[] result = declaredMethodsCache.get(clazz);
|
||||
if (result == null) {
|
||||
result = clazz.getDeclaredMethods();
|
||||
declaredMethodsCache.put(clazz, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given callback on all fields in the target class, going up the
|
||||
* class hierarchy to get all declared fields.
|
||||
|
|
|
|||
Loading…
Reference in New Issue