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