From 62c923711d86bad0cd662bd6afba1ac87a727440 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 12 Mar 2019 22:44:15 +0100 Subject: [PATCH] Consistent use of empty array constants in ReflectionUtils Closes gh-22567 --- .../springframework/util/ReflectionUtils.java | 582 +++++++++--------- 1 file changed, 297 insertions(+), 285 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 56c2d4fcee9..3788d5db1f9 100644 --- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java @@ -66,9 +66,9 @@ public abstract class ReflectionUtils { */ private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; - private static final Method[] NO_METHODS = {}; + private static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; - private static final Field[] NO_FIELDS = {}; + private static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; /** @@ -83,163 +83,7 @@ public abstract class ReflectionUtils { private static final Map, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256); - /** - * Attempt to find a {@link Field field} on the supplied {@link Class} with the - * supplied {@code name}. Searches all superclasses up to {@link Object}. - * @param clazz the class to introspect - * @param name the name of the field - * @return the corresponding Field object, or {@code null} if not found - */ - @Nullable - public static Field findField(Class clazz, String name) { - return findField(clazz, name, null); - } - - /** - * Attempt to find a {@link Field field} on the supplied {@link Class} with the - * supplied {@code name} and/or {@link Class type}. Searches all superclasses - * up to {@link Object}. - * @param clazz the class to introspect - * @param name the name of the field (may be {@code null} if type is specified) - * @param type the type of the field (may be {@code null} if name is specified) - * @return the corresponding Field object, or {@code null} if not found - */ - @Nullable - public static Field findField(Class clazz, @Nullable String name, @Nullable Class type) { - Assert.notNull(clazz, "Class must not be null"); - Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); - Class searchType = clazz; - while (Object.class != searchType && searchType != null) { - Field[] fields = getDeclaredFields(searchType); - for (Field field : fields) { - if ((name == null || name.equals(field.getName())) && - (type == null || type.equals(field.getType()))) { - return field; - } - } - searchType = searchType.getSuperclass(); - } - return null; - } - - /** - * Set the field represented by the supplied {@link Field field object} on the - * specified {@link Object target object} to the specified {@code value}. - * In accordance with {@link Field#set(Object, Object)} semantics, the new value - * is automatically unwrapped if the underlying field has a primitive type. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * @param field the field to set - * @param target the target object on which to set the field - * @param value the value to set (may be {@code null}) - */ - public static void setField(Field field, @Nullable Object target, @Nullable Object value) { - try { - field.set(target, value); - } - catch (IllegalAccessException ex) { - handleReflectionException(ex); - throw new IllegalStateException( - "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); - } - } - - /** - * Get the field represented by the supplied {@link Field field object} on the - * specified {@link Object target object}. In accordance with {@link Field#get(Object)} - * semantics, the returned value is automatically wrapped if the underlying field - * has a primitive type. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * @param field the field to get - * @param target the target object from which to get the field - * @return the field's current value - */ - @Nullable - public static Object getField(Field field, @Nullable Object target) { - try { - return field.get(target); - } - catch (IllegalAccessException ex) { - handleReflectionException(ex); - throw new IllegalStateException( - "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); - } - } - - /** - * Attempt to find a {@link Method} on the supplied class with the supplied name - * and no parameters. Searches all superclasses up to {@code Object}. - *

Returns {@code null} if no {@link Method} can be found. - * @param clazz the class to introspect - * @param name the name of the method - * @return the Method object, or {@code null} if none found - */ - @Nullable - public static Method findMethod(Class clazz, String name) { - return findMethod(clazz, name, new Class[0]); - } - - /** - * Attempt to find a {@link Method} on the supplied class with the supplied name - * and parameter types. Searches all superclasses up to {@code Object}. - *

Returns {@code null} if no {@link Method} can be found. - * @param clazz the class to introspect - * @param name the name of the method - * @param paramTypes the parameter types of the method - * (may be {@code null} to indicate any signature) - * @return the Method object, or {@code null} if none found - */ - @Nullable - public static Method findMethod(Class clazz, String name, @Nullable Class... paramTypes) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(name, "Method name must not be null"); - Class searchType = clazz; - while (searchType != null) { - Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); - for (Method method : methods) { - if (name.equals(method.getName()) && - (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { - return method; - } - } - searchType = searchType.getSuperclass(); - } - return null; - } - - /** - * Invoke the specified {@link Method} against the supplied target object with no arguments. - * The target object can be {@code null} when invoking a static {@link Method}. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. - * @param method the method to invoke - * @param target the target object to invoke the method on - * @return the invocation result, if any - * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) - */ - @Nullable - public static Object invokeMethod(Method method, @Nullable Object target) { - return invokeMethod(method, target, new Object[0]); - } - - /** - * Invoke the specified {@link Method} against the supplied target object with the - * supplied arguments. The target object can be {@code null} when invoking a - * static {@link Method}. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. - * @param method the method to invoke - * @param target the target object to invoke the method on - * @param args the invocation arguments (may be {@code null}) - * @return the invocation result, if any - */ - @Nullable - public static Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { - try { - return method.invoke(target, args); - } - catch (Exception ex) { - handleReflectionException(ex); - } - throw new IllegalStateException("Should never get here"); - } + // Exception handling /** * Handle the given reflection exception. Should only be called if no @@ -319,161 +163,138 @@ public abstract class ReflectionUtils { throw new UndeclaredThrowableException(ex); } - /** - * Determine whether the given method explicitly declares the given - * exception or one of its superclasses, which means that an exception - * of that type can be propagated as-is within a reflective invocation. - * @param method the declaring method - * @param exceptionType the exception to throw - * @return {@code true} if the exception can be thrown as-is; - * {@code false} if it needs to be wrapped - */ - public static boolean declaresException(Method method, Class exceptionType) { - Assert.notNull(method, "Method must not be null"); - Class[] declaredExceptions = method.getExceptionTypes(); - for (Class declaredException : declaredExceptions) { - if (declaredException.isAssignableFrom(exceptionType)) { - return true; - } - } - return false; - } + + // Constructor handling /** - * Determine whether the given field is a "public static final" constant. - * @param field the field to check + * Obtain an accessible constructor for the given class and parameters. + * @param clazz the clazz to check + * @param parameterTypes the parameter types of the desired constructor + * @return the constructor reference + * @throws NoSuchMethodException if no such constructor exists + * @since 5.0 */ - public static boolean isPublicStaticFinal(Field field) { - int modifiers = field.getModifiers(); - return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); + public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) + throws NoSuchMethodException { + + Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); + makeAccessible(ctor); + return ctor; } /** - * Determine whether the given method is an "equals" method. - * @see java.lang.Object#equals(Object) + * Make the given constructor accessible, explicitly setting it accessible + * if necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param ctor the constructor to make accessible + * @see java.lang.reflect.Constructor#setAccessible */ - public static boolean isEqualsMethod(@Nullable Method method) { - if (method == null || !method.getName().equals("equals")) { - return false; + @SuppressWarnings("deprecation") // on JDK 9 + public static void makeAccessible(Constructor ctor) { + if ((!Modifier.isPublic(ctor.getModifiers()) || + !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { + ctor.setAccessible(true); } - Class[] paramTypes = method.getParameterTypes(); - return (paramTypes.length == 1 && paramTypes[0] == Object.class); } - /** - * Determine whether the given method is a "hashCode" method. - * @see java.lang.Object#hashCode() - */ - public static boolean isHashCodeMethod(@Nullable Method method) { - return (method != null && method.getName().equals("hashCode") && method.getParameterCount() == 0); - } - /** - * Determine whether the given method is a "toString" method. - * @see java.lang.Object#toString() - */ - public static boolean isToStringMethod(@Nullable Method method) { - return (method != null && method.getName().equals("toString") && method.getParameterCount() == 0); - } + // Method handling /** - * Determine whether the given method is originally declared by {@link java.lang.Object}. + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and no parameters. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @return the Method object, or {@code null} if none found */ - public static boolean isObjectMethod(@Nullable Method method) { - if (method == null) { - return false; - } - try { - Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); - return true; - } - catch (Exception ex) { - return false; - } + @Nullable + public static Method findMethod(Class clazz, String name) { + return findMethod(clazz, name, new Class[0]); } /** - * Determine whether the given method is a CGLIB 'renamed' method, - * following the pattern "CGLIB$methodName$0". - * @param renamedMethod the method to check - * @see org.springframework.cglib.proxy.Enhancer#rename + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and parameter types. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) + * @return the Method object, or {@code null} if none found */ - public static boolean isCglibRenamedMethod(Method renamedMethod) { - String name = renamedMethod.getName(); - if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { - int i = name.length() - 1; - while (i >= 0 && Character.isDigit(name.charAt(i))) { - i--; + @Nullable + public static Method findMethod(Class clazz, String name, @Nullable Class... paramTypes) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(name, "Method name must not be null"); + Class searchType = clazz; + while (searchType != null) { + Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); + for (Method method : methods) { + if (name.equals(method.getName()) && + (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { + return method; + } } - return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) && - (i < name.length() - 1) && name.charAt(i) == '$'); - } - return false; - } - - /** - * Make the given field accessible, explicitly setting it accessible if - * necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * @param field the field to make accessible - * @see java.lang.reflect.Field#setAccessible - */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Field field) { - if ((!Modifier.isPublic(field.getModifiers()) || - !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || - Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { - field.setAccessible(true); + searchType = searchType.getSuperclass(); } + return null; } /** - * Make the given method accessible, explicitly setting it accessible if - * necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * @param method the method to make accessible - * @see java.lang.reflect.Method#setAccessible + * Invoke the specified {@link Method} against the supplied target object with no arguments. + * The target object can be {@code null} when invoking a static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @return the invocation result, if any + * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Method method) { - if ((!Modifier.isPublic(method.getModifiers()) || - !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { - method.setAccessible(true); - } + @Nullable + public static Object invokeMethod(Method method, @Nullable Object target) { + return invokeMethod(method, target, new Object[0]); } /** - * Make the given constructor accessible, explicitly setting it accessible - * if necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * @param ctor the constructor to make accessible - * @see java.lang.reflect.Constructor#setAccessible + * Invoke the specified {@link Method} against the supplied target object with the + * supplied arguments. The target object can be {@code null} when invoking a + * static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @param args the invocation arguments (may be {@code null}) + * @return the invocation result, if any */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Constructor ctor) { - if ((!Modifier.isPublic(ctor.getModifiers()) || - !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { - ctor.setAccessible(true); + @Nullable + public static Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { + try { + return method.invoke(target, args); } + catch (Exception ex) { + handleReflectionException(ex); + } + throw new IllegalStateException("Should never get here"); } /** - * Obtain an accessible constructor for the given class and parameters. - * @param clazz the clazz to check - * @param parameterTypes the parameter types of the desired constructor - * @return the constructor reference - * @throws NoSuchMethodException if no such constructor exists - * @since 5.0 + * Determine whether the given method explicitly declares the given + * exception or one of its superclasses, which means that an exception + * of that type can be propagated as-is within a reflective invocation. + * @param method the declaring method + * @param exceptionType the exception to throw + * @return {@code true} if the exception can be thrown as-is; + * {@code false} if it needs to be wrapped */ - public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) - throws NoSuchMethodException { - - Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); - makeAccessible(ctor); - return ctor; + public static boolean declaresException(Method method, Class exceptionType) { + Assert.notNull(method, "Method must not be null"); + Class[] declaredExceptions = method.getExceptionTypes(); + for (Class declaredException : declaredExceptions) { + if (declaredException.isAssignableFrom(exceptionType)) { + return true; + } + } + return false; } /** @@ -555,7 +376,7 @@ public abstract class ReflectionUtils { public static Method[] getAllDeclaredMethods(Class leafClass) { final List methods = new ArrayList<>(32); doWithMethods(leafClass, methods::add); - return methods.toArray(new Method[0]); + return methods.toArray(EMPTY_METHOD_ARRAY); } /** @@ -604,7 +425,7 @@ public abstract class ReflectionUtils { methods.add(method); } }, mf); - return methods.toArray(new Method[0]); + return methods.toArray(EMPTY_METHOD_ARRAY); } /** @@ -636,7 +457,7 @@ public abstract class ReflectionUtils { else { result = declaredMethods; } - declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result)); + declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); } catch (Throwable ex) { throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + @@ -662,6 +483,168 @@ public abstract class ReflectionUtils { return result; } + /** + * Determine whether the given method is an "equals" method. + * @see java.lang.Object#equals(Object) + */ + public static boolean isEqualsMethod(@Nullable Method method) { + if (method == null || !method.getName().equals("equals")) { + return false; + } + Class[] paramTypes = method.getParameterTypes(); + return (paramTypes.length == 1 && paramTypes[0] == Object.class); + } + + /** + * Determine whether the given method is a "hashCode" method. + * @see java.lang.Object#hashCode() + */ + public static boolean isHashCodeMethod(@Nullable Method method) { + return (method != null && method.getName().equals("hashCode") && method.getParameterCount() == 0); + } + + /** + * Determine whether the given method is a "toString" method. + * @see java.lang.Object#toString() + */ + public static boolean isToStringMethod(@Nullable Method method) { + return (method != null && method.getName().equals("toString") && method.getParameterCount() == 0); + } + + /** + * Determine whether the given method is originally declared by {@link java.lang.Object}. + */ + public static boolean isObjectMethod(@Nullable Method method) { + if (method == null) { + return false; + } + try { + Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); + return true; + } + catch (Exception ex) { + return false; + } + } + + /** + * Determine whether the given method is a CGLIB 'renamed' method, + * following the pattern "CGLIB$methodName$0". + * @param renamedMethod the method to check + */ + public static boolean isCglibRenamedMethod(Method renamedMethod) { + String name = renamedMethod.getName(); + if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { + int i = name.length() - 1; + while (i >= 0 && Character.isDigit(name.charAt(i))) { + i--; + } + return (i > CGLIB_RENAMED_METHOD_PREFIX.length() && (i < name.length() - 1) && name.charAt(i) == '$'); + } + return false; + } + + /** + * Make the given method accessible, explicitly setting it accessible if + * necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param method the method to make accessible + * @see java.lang.reflect.Method#setAccessible + */ + @SuppressWarnings("deprecation") // on JDK 9 + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { + method.setAccessible(true); + } + } + + + // Field handling + + /** + * Attempt to find a {@link Field field} on the supplied {@link Class} with the + * supplied {@code name}. Searches all superclasses up to {@link Object}. + * @param clazz the class to introspect + * @param name the name of the field + * @return the corresponding Field object, or {@code null} if not found + */ + @Nullable + public static Field findField(Class clazz, String name) { + return findField(clazz, name, null); + } + + /** + * Attempt to find a {@link Field field} on the supplied {@link Class} with the + * supplied {@code name} and/or {@link Class type}. Searches all superclasses + * up to {@link Object}. + * @param clazz the class to introspect + * @param name the name of the field (may be {@code null} if type is specified) + * @param type the type of the field (may be {@code null} if name is specified) + * @return the corresponding Field object, or {@code null} if not found + */ + @Nullable + public static Field findField(Class clazz, @Nullable String name, @Nullable Class type) { + Assert.notNull(clazz, "Class must not be null"); + Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); + Class searchType = clazz; + while (Object.class != searchType && searchType != null) { + Field[] fields = getDeclaredFields(searchType); + for (Field field : fields) { + if ((name == null || name.equals(field.getName())) && + (type == null || type.equals(field.getType()))) { + return field; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + + /** + * Set the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object} to the specified {@code value}. + * In accordance with {@link Field#set(Object, Object)} semantics, the new value + * is automatically unwrapped if the underlying field has a primitive type. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * @param field the field to set + * @param target the target object on which to set the field + * @param value the value to set (may be {@code null}) + */ + public static void setField(Field field, @Nullable Object target, @Nullable Object value) { + try { + field.set(target, value); + } + catch (IllegalAccessException ex) { + handleReflectionException(ex); + throw new IllegalStateException( + "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); + } + } + + /** + * Get the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object}. In accordance with {@link Field#get(Object)} + * semantics, the returned value is automatically wrapped if the underlying field + * has a primitive type. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * @param field the field to get + * @param target the target object from which to get the field + * @return the field's current value + */ + @Nullable + public static Object getField(Field field, @Nullable Object target) { + try { + return field.get(target); + } + catch (IllegalAccessException ex) { + handleReflectionException(ex); + throw new IllegalStateException( + "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); + } + } + /** * Invoke the given callback on all locally declared fields in the given class. * @param clazz the target class to analyze @@ -735,7 +718,7 @@ public abstract class ReflectionUtils { if (result == null) { try { result = clazz.getDeclaredFields(); - declaredFieldsCache.put(clazz, (result.length == 0 ? NO_FIELDS : result)); + declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); } catch (Throwable ex) { throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + @@ -765,6 +748,35 @@ public abstract class ReflectionUtils { }, COPYABLE_FIELDS); } + /** + * Determine whether the given field is a "public static final" constant. + * @param field the field to check + */ + public static boolean isPublicStaticFinal(Field field) { + int modifiers = field.getModifiers(); + return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); + } + + /** + * Make the given field accessible, explicitly setting it accessible if + * necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param field the field to make accessible + * @see java.lang.reflect.Field#setAccessible + */ + @SuppressWarnings("deprecation") // on JDK 9 + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || + !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || + Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + + // Cache handling + /** * Clear the internal method/field cache. * @since 4.2.4