Add public variant getDeclaredMethods method

Add a public variant of `getDeclaredMethods` that defensively copies the
cached methods array. This is often more faster and more convenient
for users than calling `doWithLocalMethods`. We still retain most of the
benefits of the cache, namely fewer security manager calls and not as
many `Method` instances being created.

Closes gh-22580
This commit is contained in:
Phillip Webb 2019-03-08 17:20:32 -08:00 committed by Juergen Hoeller
parent 5044ee8fe6
commit 8ef609a1b7
2 changed files with 25 additions and 10 deletions

View File

@ -230,7 +230,9 @@ 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() : getDeclaredMethods(searchType));
Method[] methods = searchType.isInterface() ?
searchType.getMethods() :
getDeclaredMethods(searchType, false);
for (Method method : methods) {
if (name.equals(method.getName()) &&
(paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
@ -308,7 +310,7 @@ public abstract class ReflectionUtils {
* @see #doWithMethods
*/
public static void doWithLocalMethods(Class<?> clazz, MethodCallback mc) {
Method[] methods = getDeclaredMethods(clazz);
Method[] methods = getDeclaredMethods(clazz, false);
for (Method method : methods) {
try {
mc.doWith(method);
@ -345,7 +347,7 @@ public abstract class ReflectionUtils {
*/
public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
Method[] methods = getDeclaredMethods(clazz, false);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
@ -429,16 +431,22 @@ public abstract class ReflectionUtils {
}
/**
* This variant retrieves {@link Class#getDeclaredMethods()} from a local cache
* in order to avoid the JVM's SecurityManager check and defensive array copying.
* In addition, it also includes Java 8 default methods from locally implemented
* interfaces, since those are effectively to be treated just like declared methods.
* Variant of {@link Class#getDeclaredMethods()} that uses a local cache in
* order to avoid the JVM's SecurityManager check and new Method instances.
* In addition, it also includes Java 8 default methods from locally
* implemented interfaces, since those are effectively to be treated just
* like declared methods.
* @param clazz the class to introspect
* @return the cached array of methods
* @throws IllegalStateException if introspection fails
* @since 5.2
* @see Class#getDeclaredMethods()
*/
private static Method[] getDeclaredMethods(Class<?> clazz) {
public static Method[] getDeclaredMethods(Class<?> clazz) {
return getDeclaredMethods(clazz, true);
}
private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
Assert.notNull(clazz, "Class must not be null");
Method[] result = declaredMethodsCache.get(clazz);
if (result == null) {
@ -464,7 +472,7 @@ public abstract class ReflectionUtils {
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return result;
return (result.length == 0 || !defensive) ? result : result.clone();
}
@Nullable

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -360,6 +360,13 @@ public class ReflectionUtilsTests {
assertThat(totalMs, Matchers.lessThan(10L));
}
@Test
public void getDecalredMethodsReturnsCopy() {
Method[] m1 = ReflectionUtils.getDeclaredMethods(A.class);
Method[] m2 = ReflectionUtils.getDeclaredMethods(A.class);
assertThat(m1, not(sameInstance(m2)));
}
private static class ListSavingMethodCallback implements ReflectionUtils.MethodCallback {
private List<String> methodNames = new LinkedList<>();