001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Field; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.lang.reflect.UndeclaredThrowableException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.List; 028import java.util.Map; 029 030import org.springframework.lang.Nullable; 031 032/** 033 * Simple utility class for working with the reflection API and handling 034 * reflection exceptions. 035 * 036 * <p>Only intended for internal use. 037 * 038 * @author Juergen Hoeller 039 * @author Rob Harrop 040 * @author Rod Johnson 041 * @author Costin Leau 042 * @author Sam Brannen 043 * @author Chris Beams 044 * @since 1.2.2 045 */ 046public abstract class ReflectionUtils { 047 048 /** 049 * Pre-built MethodFilter that matches all non-bridge non-synthetic methods 050 * which are not declared on {@code java.lang.Object}. 051 * @since 3.0.5 052 */ 053 public static final MethodFilter USER_DECLARED_METHODS = 054 (method -> !method.isBridge() && !method.isSynthetic()); 055 056 /** 057 * Pre-built FieldFilter that matches all non-static, non-final fields. 058 */ 059 public static final FieldFilter COPYABLE_FIELDS = 060 (field -> !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()))); 061 062 063 /** 064 * Naming prefix for CGLIB-renamed methods. 065 * @see #isCglibRenamedMethod 066 */ 067 private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; 068 069 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; 070 071 private static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; 072 073 private static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; 074 075 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 076 077 078 /** 079 * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods 080 * from Java 8 based interfaces, allowing for fast iteration. 081 */ 082 private static final Map<Class<?>, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256); 083 084 /** 085 * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. 086 */ 087 private static final Map<Class<?>, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256); 088 089 090 // Exception handling 091 092 /** 093 * Handle the given reflection exception. 094 * <p>Should only be called if no checked exception is expected to be thrown 095 * by a target method, or if an error occurs while accessing a method or field. 096 * <p>Throws the underlying RuntimeException or Error in case of an 097 * InvocationTargetException with such a root cause. Throws an 098 * IllegalStateException with an appropriate message or 099 * UndeclaredThrowableException otherwise. 100 * @param ex the reflection exception to handle 101 */ 102 public static void handleReflectionException(Exception ex) { 103 if (ex instanceof NoSuchMethodException) { 104 throw new IllegalStateException("Method not found: " + ex.getMessage()); 105 } 106 if (ex instanceof IllegalAccessException) { 107 throw new IllegalStateException("Could not access method or field: " + ex.getMessage()); 108 } 109 if (ex instanceof InvocationTargetException) { 110 handleInvocationTargetException((InvocationTargetException) ex); 111 } 112 if (ex instanceof RuntimeException) { 113 throw (RuntimeException) ex; 114 } 115 throw new UndeclaredThrowableException(ex); 116 } 117 118 /** 119 * Handle the given invocation target exception. Should only be called if no 120 * checked exception is expected to be thrown by the target method. 121 * <p>Throws the underlying RuntimeException or Error in case of such a root 122 * cause. Throws an UndeclaredThrowableException otherwise. 123 * @param ex the invocation target exception to handle 124 */ 125 public static void handleInvocationTargetException(InvocationTargetException ex) { 126 rethrowRuntimeException(ex.getTargetException()); 127 } 128 129 /** 130 * Rethrow the given {@link Throwable exception}, which is presumably the 131 * <em>target exception</em> of an {@link InvocationTargetException}. 132 * Should only be called if no checked exception is expected to be thrown 133 * by the target method. 134 * <p>Rethrows the underlying exception cast to a {@link RuntimeException} or 135 * {@link Error} if appropriate; otherwise, throws an 136 * {@link UndeclaredThrowableException}. 137 * @param ex the exception to rethrow 138 * @throws RuntimeException the rethrown exception 139 */ 140 public static void rethrowRuntimeException(Throwable ex) { 141 if (ex instanceof RuntimeException) { 142 throw (RuntimeException) ex; 143 } 144 if (ex instanceof Error) { 145 throw (Error) ex; 146 } 147 throw new UndeclaredThrowableException(ex); 148 } 149 150 /** 151 * Rethrow the given {@link Throwable exception}, which is presumably the 152 * <em>target exception</em> of an {@link InvocationTargetException}. 153 * Should only be called if no checked exception is expected to be thrown 154 * by the target method. 155 * <p>Rethrows the underlying exception cast to an {@link Exception} or 156 * {@link Error} if appropriate; otherwise, throws an 157 * {@link UndeclaredThrowableException}. 158 * @param ex the exception to rethrow 159 * @throws Exception the rethrown exception (in case of a checked exception) 160 */ 161 public static void rethrowException(Throwable ex) throws Exception { 162 if (ex instanceof Exception) { 163 throw (Exception) ex; 164 } 165 if (ex instanceof Error) { 166 throw (Error) ex; 167 } 168 throw new UndeclaredThrowableException(ex); 169 } 170 171 172 // Constructor handling 173 174 /** 175 * Obtain an accessible constructor for the given class and parameters. 176 * @param clazz the clazz to check 177 * @param parameterTypes the parameter types of the desired constructor 178 * @return the constructor reference 179 * @throws NoSuchMethodException if no such constructor exists 180 * @since 5.0 181 */ 182 public static <T> Constructor<T> accessibleConstructor(Class<T> clazz, Class<?>... parameterTypes) 183 throws NoSuchMethodException { 184 185 Constructor<T> ctor = clazz.getDeclaredConstructor(parameterTypes); 186 makeAccessible(ctor); 187 return ctor; 188 } 189 190 /** 191 * Make the given constructor accessible, explicitly setting it accessible 192 * if necessary. The {@code setAccessible(true)} method is only called 193 * when actually necessary, to avoid unnecessary conflicts with a JVM 194 * SecurityManager (if active). 195 * @param ctor the constructor to make accessible 196 * @see java.lang.reflect.Constructor#setAccessible 197 */ 198 @SuppressWarnings("deprecation") // on JDK 9 199 public static void makeAccessible(Constructor<?> ctor) { 200 if ((!Modifier.isPublic(ctor.getModifiers()) || 201 !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { 202 ctor.setAccessible(true); 203 } 204 } 205 206 207 // Method handling 208 209 /** 210 * Attempt to find a {@link Method} on the supplied class with the supplied name 211 * and no parameters. Searches all superclasses up to {@code Object}. 212 * <p>Returns {@code null} if no {@link Method} can be found. 213 * @param clazz the class to introspect 214 * @param name the name of the method 215 * @return the Method object, or {@code null} if none found 216 */ 217 @Nullable 218 public static Method findMethod(Class<?> clazz, String name) { 219 return findMethod(clazz, name, EMPTY_CLASS_ARRAY); 220 } 221 222 /** 223 * Attempt to find a {@link Method} on the supplied class with the supplied name 224 * and parameter types. Searches all superclasses up to {@code Object}. 225 * <p>Returns {@code null} if no {@link Method} can be found. 226 * @param clazz the class to introspect 227 * @param name the name of the method 228 * @param paramTypes the parameter types of the method 229 * (may be {@code null} to indicate any signature) 230 * @return the Method object, or {@code null} if none found 231 */ 232 @Nullable 233 public static Method findMethod(Class<?> clazz, String name, @Nullable Class<?>... paramTypes) { 234 Assert.notNull(clazz, "Class must not be null"); 235 Assert.notNull(name, "Method name must not be null"); 236 Class<?> searchType = clazz; 237 while (searchType != null) { 238 Method[] methods = (searchType.isInterface() ? searchType.getMethods() : 239 getDeclaredMethods(searchType, false)); 240 for (Method method : methods) { 241 if (name.equals(method.getName()) && (paramTypes == null || hasSameParams(method, paramTypes))) { 242 return method; 243 } 244 } 245 searchType = searchType.getSuperclass(); 246 } 247 return null; 248 } 249 250 private static boolean hasSameParams(Method method, Class<?>[] paramTypes) { 251 return (paramTypes.length == method.getParameterCount() && 252 Arrays.equals(paramTypes, method.getParameterTypes())); 253 } 254 255 /** 256 * Invoke the specified {@link Method} against the supplied target object with no arguments. 257 * The target object can be {@code null} when invoking a static {@link Method}. 258 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 259 * @param method the method to invoke 260 * @param target the target object to invoke the method on 261 * @return the invocation result, if any 262 * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) 263 */ 264 @Nullable 265 public static Object invokeMethod(Method method, @Nullable Object target) { 266 return invokeMethod(method, target, EMPTY_OBJECT_ARRAY); 267 } 268 269 /** 270 * Invoke the specified {@link Method} against the supplied target object with the 271 * supplied arguments. The target object can be {@code null} when invoking a 272 * static {@link Method}. 273 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}. 274 * @param method the method to invoke 275 * @param target the target object to invoke the method on 276 * @param args the invocation arguments (may be {@code null}) 277 * @return the invocation result, if any 278 */ 279 @Nullable 280 public static Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { 281 try { 282 return method.invoke(target, args); 283 } 284 catch (Exception ex) { 285 handleReflectionException(ex); 286 } 287 throw new IllegalStateException("Should never get here"); 288 } 289 290 /** 291 * Determine whether the given method explicitly declares the given 292 * exception or one of its superclasses, which means that an exception 293 * of that type can be propagated as-is within a reflective invocation. 294 * @param method the declaring method 295 * @param exceptionType the exception to throw 296 * @return {@code true} if the exception can be thrown as-is; 297 * {@code false} if it needs to be wrapped 298 */ 299 public static boolean declaresException(Method method, Class<?> exceptionType) { 300 Assert.notNull(method, "Method must not be null"); 301 Class<?>[] declaredExceptions = method.getExceptionTypes(); 302 for (Class<?> declaredException : declaredExceptions) { 303 if (declaredException.isAssignableFrom(exceptionType)) { 304 return true; 305 } 306 } 307 return false; 308 } 309 310 /** 311 * Perform the given callback operation on all matching methods of the given 312 * class, as locally declared or equivalent thereof (such as default methods 313 * on Java 8 based interfaces that the given class implements). 314 * @param clazz the class to introspect 315 * @param mc the callback to invoke for each method 316 * @throws IllegalStateException if introspection fails 317 * @since 4.2 318 * @see #doWithMethods 319 */ 320 public static void doWithLocalMethods(Class<?> clazz, MethodCallback mc) { 321 Method[] methods = getDeclaredMethods(clazz, false); 322 for (Method method : methods) { 323 try { 324 mc.doWith(method); 325 } 326 catch (IllegalAccessException ex) { 327 throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); 328 } 329 } 330 } 331 332 /** 333 * Perform the given callback operation on all matching methods of the given 334 * class and superclasses. 335 * <p>The same named method occurring on subclass and superclass will appear 336 * twice, unless excluded by a {@link MethodFilter}. 337 * @param clazz the class to introspect 338 * @param mc the callback to invoke for each method 339 * @throws IllegalStateException if introspection fails 340 * @see #doWithMethods(Class, MethodCallback, MethodFilter) 341 */ 342 public static void doWithMethods(Class<?> clazz, MethodCallback mc) { 343 doWithMethods(clazz, mc, null); 344 } 345 346 /** 347 * Perform the given callback operation on all matching methods of the given 348 * class and superclasses (or given interface and super-interfaces). 349 * <p>The same named method occurring on subclass and superclass will appear 350 * twice, unless excluded by the specified {@link MethodFilter}. 351 * @param clazz the class to introspect 352 * @param mc the callback to invoke for each method 353 * @param mf the filter that determines the methods to apply the callback to 354 * @throws IllegalStateException if introspection fails 355 */ 356 public static void doWithMethods(Class<?> clazz, MethodCallback mc, @Nullable MethodFilter mf) { 357 // Keep backing up the inheritance hierarchy. 358 Method[] methods = getDeclaredMethods(clazz, false); 359 for (Method method : methods) { 360 if (mf != null && !mf.matches(method)) { 361 continue; 362 } 363 try { 364 mc.doWith(method); 365 } 366 catch (IllegalAccessException ex) { 367 throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); 368 } 369 } 370 if (clazz.getSuperclass() != null && (mf != USER_DECLARED_METHODS || clazz.getSuperclass() != Object.class)) { 371 doWithMethods(clazz.getSuperclass(), mc, mf); 372 } 373 else if (clazz.isInterface()) { 374 for (Class<?> superIfc : clazz.getInterfaces()) { 375 doWithMethods(superIfc, mc, mf); 376 } 377 } 378 } 379 380 /** 381 * Get all declared methods on the leaf class and all superclasses. 382 * Leaf class methods are included first. 383 * @param leafClass the class to introspect 384 * @throws IllegalStateException if introspection fails 385 */ 386 public static Method[] getAllDeclaredMethods(Class<?> leafClass) { 387 final List<Method> methods = new ArrayList<>(32); 388 doWithMethods(leafClass, methods::add); 389 return methods.toArray(EMPTY_METHOD_ARRAY); 390 } 391 392 /** 393 * Get the unique set of declared methods on the leaf class and all superclasses. 394 * Leaf class methods are included first and while traversing the superclass hierarchy 395 * any methods found with signatures matching a method already included are filtered out. 396 * @param leafClass the class to introspect 397 * @throws IllegalStateException if introspection fails 398 */ 399 public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) { 400 return getUniqueDeclaredMethods(leafClass, null); 401 } 402 403 /** 404 * Get the unique set of declared methods on the leaf class and all superclasses. 405 * Leaf class methods are included first and while traversing the superclass hierarchy 406 * any methods found with signatures matching a method already included are filtered out. 407 * @param leafClass the class to introspect 408 * @param mf the filter that determines the methods to take into account 409 * @throws IllegalStateException if introspection fails 410 * @since 5.2 411 */ 412 public static Method[] getUniqueDeclaredMethods(Class<?> leafClass, @Nullable MethodFilter mf) { 413 final List<Method> methods = new ArrayList<>(32); 414 doWithMethods(leafClass, method -> { 415 boolean knownSignature = false; 416 Method methodBeingOverriddenWithCovariantReturnType = null; 417 for (Method existingMethod : methods) { 418 if (method.getName().equals(existingMethod.getName()) && 419 method.getParameterCount() == existingMethod.getParameterCount() && 420 Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) { 421 // Is this a covariant return type situation? 422 if (existingMethod.getReturnType() != method.getReturnType() && 423 existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) { 424 methodBeingOverriddenWithCovariantReturnType = existingMethod; 425 } 426 else { 427 knownSignature = true; 428 } 429 break; 430 } 431 } 432 if (methodBeingOverriddenWithCovariantReturnType != null) { 433 methods.remove(methodBeingOverriddenWithCovariantReturnType); 434 } 435 if (!knownSignature && !isCglibRenamedMethod(method)) { 436 methods.add(method); 437 } 438 }, mf); 439 return methods.toArray(EMPTY_METHOD_ARRAY); 440 } 441 442 /** 443 * Variant of {@link Class#getDeclaredMethods()} that uses a local cache in 444 * order to avoid the JVM's SecurityManager check and new Method instances. 445 * In addition, it also includes Java 8 default methods from locally 446 * implemented interfaces, since those are effectively to be treated just 447 * like declared methods. 448 * @param clazz the class to introspect 449 * @return the cached array of methods 450 * @throws IllegalStateException if introspection fails 451 * @since 5.2 452 * @see Class#getDeclaredMethods() 453 */ 454 public static Method[] getDeclaredMethods(Class<?> clazz) { 455 return getDeclaredMethods(clazz, true); 456 } 457 458 private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) { 459 Assert.notNull(clazz, "Class must not be null"); 460 Method[] result = declaredMethodsCache.get(clazz); 461 if (result == null) { 462 try { 463 Method[] declaredMethods = clazz.getDeclaredMethods(); 464 List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz); 465 if (defaultMethods != null) { 466 result = new Method[declaredMethods.length + defaultMethods.size()]; 467 System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); 468 int index = declaredMethods.length; 469 for (Method defaultMethod : defaultMethods) { 470 result[index] = defaultMethod; 471 index++; 472 } 473 } 474 else { 475 result = declaredMethods; 476 } 477 declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); 478 } 479 catch (Throwable ex) { 480 throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + 481 "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); 482 } 483 } 484 return (result.length == 0 || !defensive) ? result : result.clone(); 485 } 486 487 @Nullable 488 private static List<Method> findConcreteMethodsOnInterfaces(Class<?> clazz) { 489 List<Method> result = null; 490 for (Class<?> ifc : clazz.getInterfaces()) { 491 for (Method ifcMethod : ifc.getMethods()) { 492 if (!Modifier.isAbstract(ifcMethod.getModifiers())) { 493 if (result == null) { 494 result = new ArrayList<>(); 495 } 496 result.add(ifcMethod); 497 } 498 } 499 } 500 return result; 501 } 502 503 /** 504 * Determine whether the given method is an "equals" method. 505 * @see java.lang.Object#equals(Object) 506 */ 507 public static boolean isEqualsMethod(@Nullable Method method) { 508 if (method == null || !method.getName().equals("equals")) { 509 return false; 510 } 511 if (method.getParameterCount() != 1) { 512 return false; 513 } 514 return method.getParameterTypes()[0] == Object.class; 515 } 516 517 /** 518 * Determine whether the given method is a "hashCode" method. 519 * @see java.lang.Object#hashCode() 520 */ 521 public static boolean isHashCodeMethod(@Nullable Method method) { 522 return (method != null && method.getName().equals("hashCode") && method.getParameterCount() == 0); 523 } 524 525 /** 526 * Determine whether the given method is a "toString" method. 527 * @see java.lang.Object#toString() 528 */ 529 public static boolean isToStringMethod(@Nullable Method method) { 530 return (method != null && method.getName().equals("toString") && method.getParameterCount() == 0); 531 } 532 533 /** 534 * Determine whether the given method is originally declared by {@link java.lang.Object}. 535 */ 536 public static boolean isObjectMethod(@Nullable Method method) { 537 return (method != null && (method.getDeclaringClass() == Object.class || 538 isEqualsMethod(method) || isHashCodeMethod(method) || isToStringMethod(method))); 539 } 540 541 /** 542 * Determine whether the given method is a CGLIB 'renamed' method, 543 * following the pattern "CGLIB$methodName$0". 544 * @param renamedMethod the method to check 545 */ 546 public static boolean isCglibRenamedMethod(Method renamedMethod) { 547 String name = renamedMethod.getName(); 548 if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { 549 int i = name.length() - 1; 550 while (i >= 0 && Character.isDigit(name.charAt(i))) { 551 i--; 552 } 553 return (i > CGLIB_RENAMED_METHOD_PREFIX.length() && (i < name.length() - 1) && name.charAt(i) == '$'); 554 } 555 return false; 556 } 557 558 /** 559 * Make the given method accessible, explicitly setting it accessible if 560 * necessary. The {@code setAccessible(true)} method is only called 561 * when actually necessary, to avoid unnecessary conflicts with a JVM 562 * SecurityManager (if active). 563 * @param method the method to make accessible 564 * @see java.lang.reflect.Method#setAccessible 565 */ 566 @SuppressWarnings("deprecation") // on JDK 9 567 public static void makeAccessible(Method method) { 568 if ((!Modifier.isPublic(method.getModifiers()) || 569 !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { 570 method.setAccessible(true); 571 } 572 } 573 574 575 // Field handling 576 577 /** 578 * Attempt to find a {@link Field field} on the supplied {@link Class} with the 579 * supplied {@code name}. Searches all superclasses up to {@link Object}. 580 * @param clazz the class to introspect 581 * @param name the name of the field 582 * @return the corresponding Field object, or {@code null} if not found 583 */ 584 @Nullable 585 public static Field findField(Class<?> clazz, String name) { 586 return findField(clazz, name, null); 587 } 588 589 /** 590 * Attempt to find a {@link Field field} on the supplied {@link Class} with the 591 * supplied {@code name} and/or {@link Class type}. Searches all superclasses 592 * up to {@link Object}. 593 * @param clazz the class to introspect 594 * @param name the name of the field (may be {@code null} if type is specified) 595 * @param type the type of the field (may be {@code null} if name is specified) 596 * @return the corresponding Field object, or {@code null} if not found 597 */ 598 @Nullable 599 public static Field findField(Class<?> clazz, @Nullable String name, @Nullable Class<?> type) { 600 Assert.notNull(clazz, "Class must not be null"); 601 Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); 602 Class<?> searchType = clazz; 603 while (Object.class != searchType && searchType != null) { 604 Field[] fields = getDeclaredFields(searchType); 605 for (Field field : fields) { 606 if ((name == null || name.equals(field.getName())) && 607 (type == null || type.equals(field.getType()))) { 608 return field; 609 } 610 } 611 searchType = searchType.getSuperclass(); 612 } 613 return null; 614 } 615 616 /** 617 * Set the field represented by the supplied {@linkplain Field field object} on 618 * the specified {@linkplain Object target object} to the specified {@code value}. 619 * <p>In accordance with {@link Field#set(Object, Object)} semantics, the new value 620 * is automatically unwrapped if the underlying field has a primitive type. 621 * <p>This method does not support setting {@code static final} fields. 622 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. 623 * @param field the field to set 624 * @param target the target object on which to set the field 625 * @param value the value to set (may be {@code null}) 626 */ 627 public static void setField(Field field, @Nullable Object target, @Nullable Object value) { 628 try { 629 field.set(target, value); 630 } 631 catch (IllegalAccessException ex) { 632 handleReflectionException(ex); 633 } 634 } 635 636 /** 637 * Get the field represented by the supplied {@link Field field object} on the 638 * specified {@link Object target object}. In accordance with {@link Field#get(Object)} 639 * semantics, the returned value is automatically wrapped if the underlying field 640 * has a primitive type. 641 * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. 642 * @param field the field to get 643 * @param target the target object from which to get the field 644 * @return the field's current value 645 */ 646 @Nullable 647 public static Object getField(Field field, @Nullable Object target) { 648 try { 649 return field.get(target); 650 } 651 catch (IllegalAccessException ex) { 652 handleReflectionException(ex); 653 } 654 throw new IllegalStateException("Should never get here"); 655 } 656 657 /** 658 * Invoke the given callback on all locally declared fields in the given class. 659 * @param clazz the target class to analyze 660 * @param fc the callback to invoke for each field 661 * @throws IllegalStateException if introspection fails 662 * @since 4.2 663 * @see #doWithFields 664 */ 665 public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) { 666 for (Field field : getDeclaredFields(clazz)) { 667 try { 668 fc.doWith(field); 669 } 670 catch (IllegalAccessException ex) { 671 throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); 672 } 673 } 674 } 675 676 /** 677 * Invoke the given callback on all fields in the target class, going up the 678 * class hierarchy to get all declared fields. 679 * @param clazz the target class to analyze 680 * @param fc the callback to invoke for each field 681 * @throws IllegalStateException if introspection fails 682 */ 683 public static void doWithFields(Class<?> clazz, FieldCallback fc) { 684 doWithFields(clazz, fc, null); 685 } 686 687 /** 688 * Invoke the given callback on all fields in the target class, going up the 689 * class hierarchy to get all declared fields. 690 * @param clazz the target class to analyze 691 * @param fc the callback to invoke for each field 692 * @param ff the filter that determines the fields to apply the callback to 693 * @throws IllegalStateException if introspection fails 694 */ 695 public static void doWithFields(Class<?> clazz, FieldCallback fc, @Nullable FieldFilter ff) { 696 // Keep backing up the inheritance hierarchy. 697 Class<?> targetClass = clazz; 698 do { 699 Field[] fields = getDeclaredFields(targetClass); 700 for (Field field : fields) { 701 if (ff != null && !ff.matches(field)) { 702 continue; 703 } 704 try { 705 fc.doWith(field); 706 } 707 catch (IllegalAccessException ex) { 708 throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); 709 } 710 } 711 targetClass = targetClass.getSuperclass(); 712 } 713 while (targetClass != null && targetClass != Object.class); 714 } 715 716 /** 717 * This variant retrieves {@link Class#getDeclaredFields()} from a local cache 718 * in order to avoid the JVM's SecurityManager check and defensive array copying. 719 * @param clazz the class to introspect 720 * @return the cached array of fields 721 * @throws IllegalStateException if introspection fails 722 * @see Class#getDeclaredFields() 723 */ 724 private static Field[] getDeclaredFields(Class<?> clazz) { 725 Assert.notNull(clazz, "Class must not be null"); 726 Field[] result = declaredFieldsCache.get(clazz); 727 if (result == null) { 728 try { 729 result = clazz.getDeclaredFields(); 730 declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); 731 } 732 catch (Throwable ex) { 733 throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + 734 "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); 735 } 736 } 737 return result; 738 } 739 740 /** 741 * Given the source object and the destination, which must be the same class 742 * or a subclass, copy all fields, including inherited fields. Designed to 743 * work on objects with public no-arg constructors. 744 * @throws IllegalStateException if introspection fails 745 */ 746 public static void shallowCopyFieldState(final Object src, final Object dest) { 747 Assert.notNull(src, "Source for field copy cannot be null"); 748 Assert.notNull(dest, "Destination for field copy cannot be null"); 749 if (!src.getClass().isAssignableFrom(dest.getClass())) { 750 throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() + 751 "] must be same or subclass as source class [" + src.getClass().getName() + "]"); 752 } 753 doWithFields(src.getClass(), field -> { 754 makeAccessible(field); 755 Object srcValue = field.get(src); 756 field.set(dest, srcValue); 757 }, COPYABLE_FIELDS); 758 } 759 760 /** 761 * Determine whether the given field is a "public static final" constant. 762 * @param field the field to check 763 */ 764 public static boolean isPublicStaticFinal(Field field) { 765 int modifiers = field.getModifiers(); 766 return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); 767 } 768 769 /** 770 * Make the given field accessible, explicitly setting it accessible if 771 * necessary. The {@code setAccessible(true)} method is only called 772 * when actually necessary, to avoid unnecessary conflicts with a JVM 773 * SecurityManager (if active). 774 * @param field the field to make accessible 775 * @see java.lang.reflect.Field#setAccessible 776 */ 777 @SuppressWarnings("deprecation") // on JDK 9 778 public static void makeAccessible(Field field) { 779 if ((!Modifier.isPublic(field.getModifiers()) || 780 !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || 781 Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { 782 field.setAccessible(true); 783 } 784 } 785 786 787 // Cache handling 788 789 /** 790 * Clear the internal method/field cache. 791 * @since 4.2.4 792 */ 793 public static void clearCache() { 794 declaredMethodsCache.clear(); 795 declaredFieldsCache.clear(); 796 } 797 798 799 /** 800 * Action to take on each method. 801 */ 802 @FunctionalInterface 803 public interface MethodCallback { 804 805 /** 806 * Perform an operation using the given method. 807 * @param method the method to operate on 808 */ 809 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; 810 } 811 812 813 /** 814 * Callback optionally used to filter methods to be operated on by a method callback. 815 */ 816 @FunctionalInterface 817 public interface MethodFilter { 818 819 /** 820 * Determine whether the given method matches. 821 * @param method the method to check 822 */ 823 boolean matches(Method method); 824 } 825 826 827 /** 828 * Callback interface invoked on each field in the hierarchy. 829 */ 830 @FunctionalInterface 831 public interface FieldCallback { 832 833 /** 834 * Perform an operation using the given field. 835 * @param field the field to operate on 836 */ 837 void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; 838 } 839 840 841 /** 842 * Callback optionally used to filter fields to be operated on by a field callback. 843 */ 844 @FunctionalInterface 845 public interface FieldFilter { 846 847 /** 848 * Determine whether the given field matches. 849 * @param field the field to check 850 */ 851 boolean matches(Field field); 852 } 853 854}