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.test.util; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Method; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.lang.Nullable; 026import org.springframework.util.Assert; 027import org.springframework.util.ClassUtils; 028import org.springframework.util.MethodInvoker; 029import org.springframework.util.ObjectUtils; 030import org.springframework.util.ReflectionUtils; 031import org.springframework.util.StringUtils; 032 033/** 034 * {@code ReflectionTestUtils} is a collection of reflection-based utility 035 * methods for use in unit and integration testing scenarios. 036 * 037 * <p>There are often times when it would be beneficial to be able to set a 038 * non-{@code public} field, invoke a non-{@code public} setter method, or 039 * invoke a non-{@code public} <em>configuration</em> or <em>lifecycle</em> 040 * callback method when testing code involving, for example: 041 * 042 * <ul> 043 * <li>ORM frameworks such as JPA and Hibernate which condone the usage of 044 * {@code private} or {@code protected} field access as opposed to 045 * {@code public} setter methods for properties in a domain entity.</li> 046 * <li>Spring's support for annotations such as 047 * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, 048 * {@link javax.inject.Inject @Inject}, and 049 * {@link javax.annotation.Resource @Resource} which provides dependency 050 * injection for {@code private} or {@code protected} fields, setter methods, 051 * and configuration methods.</li> 052 * <li>Use of annotations such as {@link javax.annotation.PostConstruct @PostConstruct} 053 * and {@link javax.annotation.PreDestroy @PreDestroy} for lifecycle callback 054 * methods.</li> 055 * </ul> 056 * 057 * <p>In addition, several methods in this class provide support for {@code static} 058 * fields and {@code static} methods — for example, 059 * {@link #setField(Class, String, Object)}, {@link #getField(Class, String)}, 060 * {@link #invokeMethod(Class, String, Object...)}, 061 * {@link #invokeMethod(Object, Class, String, Object...)}, etc. 062 * 063 * @author Sam Brannen 064 * @author Juergen Hoeller 065 * @since 2.5 066 * @see ReflectionUtils 067 * @see AopTestUtils 068 */ 069public abstract class ReflectionTestUtils { 070 071 private static final String SETTER_PREFIX = "set"; 072 073 private static final String GETTER_PREFIX = "get"; 074 075 private static final Log logger = LogFactory.getLog(ReflectionTestUtils.class); 076 077 private static final boolean springAopPresent = ClassUtils.isPresent( 078 "org.springframework.aop.framework.Advised", ReflectionTestUtils.class.getClassLoader()); 079 080 081 /** 082 * Set the {@linkplain Field field} with the given {@code name} on the 083 * provided {@code targetObject} to the supplied {@code value}. 084 * <p>This method delegates to {@link #setField(Object, String, Object, Class)}, 085 * supplying {@code null} for the {@code type} argument. 086 * @param targetObject the target object on which to set the field; never {@code null} 087 * @param name the name of the field to set; never {@code null} 088 * @param value the value to set 089 */ 090 public static void setField(Object targetObject, String name, @Nullable Object value) { 091 setField(targetObject, name, value, null); 092 } 093 094 /** 095 * Set the {@linkplain Field field} with the given {@code name}/{@code type} 096 * on the provided {@code targetObject} to the supplied {@code value}. 097 * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)}, 098 * supplying {@code null} for the {@code targetClass} argument. 099 * @param targetObject the target object on which to set the field; never {@code null} 100 * @param name the name of the field to set; may be {@code null} if 101 * {@code type} is specified 102 * @param value the value to set 103 * @param type the type of the field to set; may be {@code null} if 104 * {@code name} is specified 105 */ 106 public static void setField(Object targetObject, @Nullable String name, @Nullable Object value, @Nullable Class<?> type) { 107 setField(targetObject, null, name, value, type); 108 } 109 110 /** 111 * Set the static {@linkplain Field field} with the given {@code name} on 112 * the provided {@code targetClass} to the supplied {@code value}. 113 * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)}, 114 * supplying {@code null} for the {@code targetObject} and {@code type} arguments. 115 * <p>This method does not support setting {@code static final} fields. 116 * @param targetClass the target class on which to set the static field; 117 * never {@code null} 118 * @param name the name of the field to set; never {@code null} 119 * @param value the value to set 120 * @since 4.2 121 */ 122 public static void setField(Class<?> targetClass, String name, @Nullable Object value) { 123 setField(null, targetClass, name, value, null); 124 } 125 126 /** 127 * Set the static {@linkplain Field field} with the given 128 * {@code name}/{@code type} on the provided {@code targetClass} to 129 * the supplied {@code value}. 130 * <p>This method delegates to {@link #setField(Object, Class, String, Object, Class)}, 131 * supplying {@code null} for the {@code targetObject} argument. 132 * <p>This method does not support setting {@code static final} fields. 133 * @param targetClass the target class on which to set the static field; 134 * never {@code null} 135 * @param name the name of the field to set; may be {@code null} if 136 * {@code type} is specified 137 * @param value the value to set 138 * @param type the type of the field to set; may be {@code null} if 139 * {@code name} is specified 140 * @since 4.2 141 */ 142 public static void setField( 143 Class<?> targetClass, @Nullable String name, @Nullable Object value, @Nullable Class<?> type) { 144 145 setField(null, targetClass, name, value, type); 146 } 147 148 /** 149 * Set the {@linkplain Field field} with the given {@code name}/{@code type} 150 * on the provided {@code targetObject}/{@code targetClass} to the supplied 151 * {@code value}. 152 * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will 153 * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing 154 * the field to be set on the ultimate target of the proxy. 155 * <p>This method traverses the class hierarchy in search of the desired 156 * field. In addition, an attempt will be made to make non-{@code public} 157 * fields <em>accessible</em>, thus allowing one to set {@code protected}, 158 * {@code private}, and <em>package-private</em> fields. 159 * <p>This method does not support setting {@code static final} fields. 160 * @param targetObject the target object on which to set the field; may be 161 * {@code null} if the field is static 162 * @param targetClass the target class on which to set the field; may 163 * be {@code null} if the field is an instance field 164 * @param name the name of the field to set; may be {@code null} if 165 * {@code type} is specified 166 * @param value the value to set 167 * @param type the type of the field to set; may be {@code null} if 168 * {@code name} is specified 169 * @since 4.2 170 * @see ReflectionUtils#findField(Class, String, Class) 171 * @see ReflectionUtils#makeAccessible(Field) 172 * @see ReflectionUtils#setField(Field, Object, Object) 173 * @see AopTestUtils#getUltimateTargetObject(Object) 174 */ 175 public static void setField(@Nullable Object targetObject, @Nullable Class<?> targetClass, 176 @Nullable String name, @Nullable Object value, @Nullable Class<?> type) { 177 178 Assert.isTrue(targetObject != null || targetClass != null, 179 "Either targetObject or targetClass for the field must be specified"); 180 181 if (targetObject != null && springAopPresent) { 182 targetObject = AopTestUtils.getUltimateTargetObject(targetObject); 183 } 184 if (targetClass == null) { 185 targetClass = targetObject.getClass(); 186 } 187 188 Field field = ReflectionUtils.findField(targetClass, name, type); 189 if (field == null) { 190 throw new IllegalArgumentException(String.format( 191 "Could not find field '%s' of type [%s] on %s or target class [%s]", name, type, 192 safeToString(targetObject), targetClass)); 193 } 194 195 if (logger.isDebugEnabled()) { 196 logger.debug(String.format( 197 "Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type, 198 safeToString(targetObject), targetClass, value)); 199 } 200 ReflectionUtils.makeAccessible(field); 201 ReflectionUtils.setField(field, targetObject, value); 202 } 203 204 /** 205 * Get the value of the {@linkplain Field field} with the given {@code name} 206 * from the provided {@code targetObject}. 207 * <p>This method delegates to {@link #getField(Object, Class, String)}, 208 * supplying {@code null} for the {@code targetClass} argument. 209 * @param targetObject the target object from which to get the field; 210 * never {@code null} 211 * @param name the name of the field to get; never {@code null} 212 * @return the field's current value 213 * @see #getField(Class, String) 214 */ 215 @Nullable 216 public static Object getField(Object targetObject, String name) { 217 return getField(targetObject, null, name); 218 } 219 220 /** 221 * Get the value of the static {@linkplain Field field} with the given 222 * {@code name} from the provided {@code targetClass}. 223 * <p>This method delegates to {@link #getField(Object, Class, String)}, 224 * supplying {@code null} for the {@code targetObject} argument. 225 * @param targetClass the target class from which to get the static field; 226 * never {@code null} 227 * @param name the name of the field to get; never {@code null} 228 * @return the field's current value 229 * @since 4.2 230 * @see #getField(Object, String) 231 */ 232 @Nullable 233 public static Object getField(Class<?> targetClass, String name) { 234 return getField(null, targetClass, name); 235 } 236 237 /** 238 * Get the value of the {@linkplain Field field} with the given {@code name} 239 * from the provided {@code targetObject}/{@code targetClass}. 240 * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will 241 * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing 242 * the field to be retrieved from the ultimate target of the proxy. 243 * <p>This method traverses the class hierarchy in search of the desired 244 * field. In addition, an attempt will be made to make non-{@code public} 245 * fields <em>accessible</em>, thus allowing one to get {@code protected}, 246 * {@code private}, and <em>package-private</em> fields. 247 * @param targetObject the target object from which to get the field; may be 248 * {@code null} if the field is static 249 * @param targetClass the target class from which to get the field; may 250 * be {@code null} if the field is an instance field 251 * @param name the name of the field to get; never {@code null} 252 * @return the field's current value 253 * @since 4.2 254 * @see #getField(Object, String) 255 * @see #getField(Class, String) 256 * @see ReflectionUtils#findField(Class, String, Class) 257 * @see ReflectionUtils#makeAccessible(Field) 258 * @see ReflectionUtils#getField(Field, Object) 259 * @see AopTestUtils#getUltimateTargetObject(Object) 260 */ 261 @Nullable 262 public static Object getField(@Nullable Object targetObject, @Nullable Class<?> targetClass, String name) { 263 Assert.isTrue(targetObject != null || targetClass != null, 264 "Either targetObject or targetClass for the field must be specified"); 265 266 if (targetObject != null && springAopPresent) { 267 targetObject = AopTestUtils.getUltimateTargetObject(targetObject); 268 } 269 if (targetClass == null) { 270 targetClass = targetObject.getClass(); 271 } 272 273 Field field = ReflectionUtils.findField(targetClass, name); 274 if (field == null) { 275 throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]", 276 name, safeToString(targetObject), targetClass)); 277 } 278 279 if (logger.isDebugEnabled()) { 280 logger.debug(String.format("Getting field '%s' from %s or target class [%s]", name, 281 safeToString(targetObject), targetClass)); 282 } 283 ReflectionUtils.makeAccessible(field); 284 return ReflectionUtils.getField(field, targetObject); 285 } 286 287 /** 288 * Invoke the setter method with the given {@code name} on the supplied 289 * target object with the supplied {@code value}. 290 * <p>This method traverses the class hierarchy in search of the desired 291 * method. In addition, an attempt will be made to make non-{@code public} 292 * methods <em>accessible</em>, thus allowing one to invoke {@code protected}, 293 * {@code private}, and <em>package-private</em> setter methods. 294 * <p>In addition, this method supports JavaBean-style <em>property</em> 295 * names. For example, if you wish to set the {@code name} property on the 296 * target object, you may pass either "name" or 297 * "setName" as the method name. 298 * @param target the target object on which to invoke the specified setter 299 * method 300 * @param name the name of the setter method to invoke or the corresponding 301 * property name 302 * @param value the value to provide to the setter method 303 * @see ReflectionUtils#findMethod(Class, String, Class[]) 304 * @see ReflectionUtils#makeAccessible(Method) 305 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 306 */ 307 public static void invokeSetterMethod(Object target, String name, Object value) { 308 invokeSetterMethod(target, name, value, null); 309 } 310 311 /** 312 * Invoke the setter method with the given {@code name} on the supplied 313 * target object with the supplied {@code value}. 314 * <p>This method traverses the class hierarchy in search of the desired 315 * method. In addition, an attempt will be made to make non-{@code public} 316 * methods <em>accessible</em>, thus allowing one to invoke {@code protected}, 317 * {@code private}, and <em>package-private</em> setter methods. 318 * <p>In addition, this method supports JavaBean-style <em>property</em> 319 * names. For example, if you wish to set the {@code name} property on the 320 * target object, you may pass either "name" or 321 * "setName" as the method name. 322 * @param target the target object on which to invoke the specified setter 323 * method 324 * @param name the name of the setter method to invoke or the corresponding 325 * property name 326 * @param value the value to provide to the setter method 327 * @param type the formal parameter type declared by the setter method 328 * @see ReflectionUtils#findMethod(Class, String, Class[]) 329 * @see ReflectionUtils#makeAccessible(Method) 330 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 331 */ 332 public static void invokeSetterMethod(Object target, String name, @Nullable Object value, @Nullable Class<?> type) { 333 Assert.notNull(target, "Target object must not be null"); 334 Assert.hasText(name, "Method name must not be empty"); 335 Class<?>[] paramTypes = (type != null ? new Class<?>[] {type} : null); 336 337 String setterMethodName = name; 338 if (!name.startsWith(SETTER_PREFIX)) { 339 setterMethodName = SETTER_PREFIX + StringUtils.capitalize(name); 340 } 341 342 Method method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes); 343 if (method == null && !setterMethodName.equals(name)) { 344 setterMethodName = name; 345 method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes); 346 } 347 if (method == null) { 348 throw new IllegalArgumentException(String.format( 349 "Could not find setter method '%s' on %s with parameter type [%s]", setterMethodName, 350 safeToString(target), type)); 351 } 352 353 if (logger.isDebugEnabled()) { 354 logger.debug(String.format("Invoking setter method '%s' on %s with value [%s]", setterMethodName, 355 safeToString(target), value)); 356 } 357 358 ReflectionUtils.makeAccessible(method); 359 ReflectionUtils.invokeMethod(method, target, value); 360 } 361 362 /** 363 * Invoke the getter method with the given {@code name} on the supplied 364 * target object with the supplied {@code value}. 365 * <p>This method traverses the class hierarchy in search of the desired 366 * method. In addition, an attempt will be made to make non-{@code public} 367 * methods <em>accessible</em>, thus allowing one to invoke {@code protected}, 368 * {@code private}, and <em>package-private</em> getter methods. 369 * <p>In addition, this method supports JavaBean-style <em>property</em> 370 * names. For example, if you wish to get the {@code name} property on the 371 * target object, you may pass either "name" or 372 * "getName" as the method name. 373 * @param target the target object on which to invoke the specified getter 374 * method 375 * @param name the name of the getter method to invoke or the corresponding 376 * property name 377 * @return the value returned from the invocation 378 * @see ReflectionUtils#findMethod(Class, String, Class[]) 379 * @see ReflectionUtils#makeAccessible(Method) 380 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 381 */ 382 @Nullable 383 public static Object invokeGetterMethod(Object target, String name) { 384 Assert.notNull(target, "Target object must not be null"); 385 Assert.hasText(name, "Method name must not be empty"); 386 387 String getterMethodName = name; 388 if (!name.startsWith(GETTER_PREFIX)) { 389 getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); 390 } 391 Method method = ReflectionUtils.findMethod(target.getClass(), getterMethodName); 392 if (method == null && !getterMethodName.equals(name)) { 393 getterMethodName = name; 394 method = ReflectionUtils.findMethod(target.getClass(), getterMethodName); 395 } 396 if (method == null) { 397 throw new IllegalArgumentException(String.format( 398 "Could not find getter method '%s' on %s", getterMethodName, safeToString(target))); 399 } 400 401 if (logger.isDebugEnabled()) { 402 logger.debug(String.format("Invoking getter method '%s' on %s", getterMethodName, safeToString(target))); 403 } 404 ReflectionUtils.makeAccessible(method); 405 return ReflectionUtils.invokeMethod(method, target); 406 } 407 408 /** 409 * Invoke the method with the given {@code name} on the supplied target 410 * object with the supplied arguments. 411 * <p>This method delegates to {@link #invokeMethod(Object, Class, String, Object...)}, 412 * supplying {@code null} for the {@code targetClass} argument. 413 * @param target the target object on which to invoke the specified method 414 * @param name the name of the method to invoke 415 * @param args the arguments to provide to the method 416 * @return the invocation result, if any 417 * @see #invokeMethod(Class, String, Object...) 418 * @see #invokeMethod(Object, Class, String, Object...) 419 * @see MethodInvoker 420 * @see ReflectionUtils#makeAccessible(Method) 421 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 422 * @see ReflectionUtils#handleReflectionException(Exception) 423 */ 424 @Nullable 425 public static <T> T invokeMethod(Object target, String name, Object... args) { 426 Assert.notNull(target, "Target object must not be null"); 427 return invokeMethod(target, null, name, args); 428 } 429 430 /** 431 * Invoke the static method with the given {@code name} on the supplied target 432 * class with the supplied arguments. 433 * <p>This method delegates to {@link #invokeMethod(Object, Class, String, Object...)}, 434 * supplying {@code null} for the {@code targetObject} argument. 435 * @param targetClass the target class on which to invoke the specified method 436 * @param name the name of the method to invoke 437 * @param args the arguments to provide to the method 438 * @return the invocation result, if any 439 * @since 5.2 440 * @see #invokeMethod(Object, String, Object...) 441 * @see #invokeMethod(Object, Class, String, Object...) 442 * @see MethodInvoker 443 * @see ReflectionUtils#makeAccessible(Method) 444 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 445 * @see ReflectionUtils#handleReflectionException(Exception) 446 */ 447 @Nullable 448 public static <T> T invokeMethod(Class<?> targetClass, String name, Object... args) { 449 Assert.notNull(targetClass, "Target class must not be null"); 450 return invokeMethod(null, targetClass, name, args); 451 } 452 453 /** 454 * Invoke the method with the given {@code name} on the provided 455 * {@code targetObject}/{@code targetClass} with the supplied arguments. 456 * <p>This method traverses the class hierarchy in search of the desired 457 * method. In addition, an attempt will be made to make non-{@code public} 458 * methods <em>accessible</em>, thus allowing one to invoke {@code protected}, 459 * {@code private}, and <em>package-private</em> methods. 460 * @param targetObject the target object on which to invoke the method; may 461 * be {@code null} if the method is static 462 * @param targetClass the target class on which to invoke the method; may 463 * be {@code null} if the method is an instance method 464 * @param name the name of the method to invoke 465 * @param args the arguments to provide to the method 466 * @return the invocation result, if any 467 * @since 5.2 468 * @see #invokeMethod(Object, String, Object...) 469 * @see #invokeMethod(Class, String, Object...) 470 * @see MethodInvoker 471 * @see ReflectionUtils#makeAccessible(Method) 472 * @see ReflectionUtils#invokeMethod(Method, Object, Object[]) 473 * @see ReflectionUtils#handleReflectionException(Exception) 474 */ 475 @SuppressWarnings("unchecked") 476 @Nullable 477 public static <T> T invokeMethod(@Nullable Object targetObject, @Nullable Class<?> targetClass, String name, 478 Object... args) { 479 480 Assert.isTrue(targetObject != null || targetClass != null, 481 "Either 'targetObject' or 'targetClass' for the method must be specified"); 482 Assert.hasText(name, "Method name must not be empty"); 483 484 try { 485 MethodInvoker methodInvoker = new MethodInvoker(); 486 methodInvoker.setTargetObject(targetObject); 487 if (targetClass != null) { 488 methodInvoker.setTargetClass(targetClass); 489 } 490 methodInvoker.setTargetMethod(name); 491 methodInvoker.setArguments(args); 492 methodInvoker.prepare(); 493 494 if (logger.isDebugEnabled()) { 495 logger.debug(String.format("Invoking method '%s' on %s or %s with arguments %s", name, 496 safeToString(targetObject), safeToString(targetClass), ObjectUtils.nullSafeToString(args))); 497 } 498 499 return (T) methodInvoker.invoke(); 500 } 501 catch (Exception ex) { 502 ReflectionUtils.handleReflectionException(ex); 503 throw new IllegalStateException("Should never get here"); 504 } 505 } 506 507 private static String safeToString(@Nullable Object target) { 508 try { 509 return String.format("target object [%s]", target); 510 } 511 catch (Exception ex) { 512 return String.format("target of type [%s] whose toString() method threw [%s]", 513 (target != null ? target.getClass().getName() : "unknown"), ex); 514 } 515 } 516 517 private static String safeToString(@Nullable Class<?> clazz) { 518 return String.format("target class [%s]", (clazz != null ? clazz.getName() : null)); 519 } 520 521}