ReflectionUtils caches Class.getDeclaredMethods() results; AnnotationUtils caches findAnnotation results

Issue: SPR-11882
master
Juergen Hoeller 10 years ago
parent b0979cbab6
commit 682a910bb6
  1. 137
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  2. 24
      spring-core/src/main/java/org/springframework/util/ReflectionUtils.java

@ -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;
}
try {
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
annotation = getAnnotation(equivalentMethod, annotationType);
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());
}
catch (NoSuchMethodException ex) {
// No equivalent method found
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());
}
}
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;
}
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);
}
}
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;
}
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…
Cancel
Save