From 8ef609a1b74ad359ae764c4d4052365ac5483fcf Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 8 Mar 2019 17:20:32 -0800 Subject: [PATCH] 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 --- .../springframework/util/ReflectionUtils.java | 26 ++++++++++++------- .../util/ReflectionUtilsTests.java | 9 ++++++- 2 files changed, 25 insertions(+), 10 deletions(-) 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 3788d5db1f9..70ed55528cb 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -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 diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index c304e53ed53..d9135b0a14a 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -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 methodNames = new LinkedList<>();