diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index f823ecd2fe..3784c9815c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -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, Boolean> annotatedInterfaceCache = new WeakHashMap, Boolean>(); + private static final Map findAnnotationCache = + new ConcurrentReferenceHashMap(256); + + private static final Map, Boolean> annotatedInterfaceCache = + new ConcurrentReferenceHashMap, 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 findAnnotation(Method method, Class 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 searchOnInterfaces(Method method, Class annotationType, Class[] ifcs) { + private static A searchOnInterfaces(Method method, Class 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 findAnnotation(Class clazz, Class annotationType) { - return findAnnotation(clazz, annotationType, new HashSet()); + AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType); + A result = (A) findAnnotationCache.get(cacheKey); + if (result == null) { + result = findAnnotation(clazz, annotationType, new HashSet()); + 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 annotationType; + + public AnnotationCacheKey(AnnotatedElement element, Class 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 { private final Class containerAnnotationType; diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java index c2f9cd05d3..1bf528e9d2 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -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, Method[]> declaredMethodsCache = + new ConcurrentReferenceHashMap, 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.