[SPR-8633] Introduced generic invokeMethod() in ReflectionTestUtils.

This commit is contained in:
Sam Brannen
2011-08-24 17:26:59 +00:00
parent 72da569aa3
commit 4d99ddba8a
4 changed files with 337 additions and 182 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,28 +22,32 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.MethodInvoker;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* <p>
* ReflectionTestUtils is a collection of reflection-based utility methods for
* use in unit and integration testing scenarios.
* </p>
* <p>
* There are often situations in which it would be beneficial to be able to set
* a non-<code>public</code> field or invoke a non-<code>public</code> setter
* method when testing code involving, for example:
* </p>
* {@code ReflectionTestUtils} is a collection of reflection-based utility
* methods for use in unit and integration testing scenarios.
*
* <p>There are often times when it would be beneficial to be able to set a
* non-{@code public} field, invoke a non-{@code public} setter method, or
* invoke a non-{@code public} <em>configuration</em> or <em>lifecycle</em>
* callback method when testing code involving, for example:
*
* <ul>
* <li>ORM frameworks such as JPA and Hibernate which condone the usage of
* <code>private</code> or <code>protected</code> field access as opposed to
* <code>public</code> setter methods for properties in a domain entity.</li>
* {@code private} or {@code protected} field access as opposed to
* {@code public} setter methods for properties in a domain entity.</li>
* <li>Spring's support for annotations such as
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired} and
* {@link javax.annotation.Resource @Resource} which provides dependency
* injection for <code>private</code> or <code>protected</code> fields, setter
* methods, and configuration methods.</li>
* injection for {@code private} or {@code protected} fields, setter methods,
* and configuration methods.</li>
* <li>Use of annotations such as {@link javax.annotation.PostConstruct @PostConstruct}
* and {@link javax.annotation.PreDestroy @PreDestroy} for lifecycle callback
* methods.</li>
* </ul>
*
* @author Sam Brannen
@@ -61,14 +65,13 @@ public class ReflectionTestUtils {
/**
* Set the {@link Field field} with the given <code>name</code> on the
* provided {@link Object target object} to the supplied <code>value</code>.
* <p>
* This method traverses the class hierarchy in search of the desired field.
* In addition, an attempt will be made to make non-<code>public</code>
* fields <em>accessible</em>, thus allowing one to set
* <code>protected</code>, <code>private</code>, and
* <em>package-private</em> fields.
* Set the {@link Field field} with the given {@code name} on the provided
* {@link Object target object} to the supplied {@code value}.
*
* <p>This method traverses the class hierarchy in search of the desired field.
* In addition, an attempt will be made to make non-{@code public} fields
* <em>accessible</em>, thus allowing one to set {@code protected},
* {@code private}, and <em>package-private</em> fields.
*
* @param target the target object on which to set the field
* @param name the name of the field to set
@@ -82,19 +85,18 @@ public class ReflectionTestUtils {
}
/**
* Set the {@link Field field} with the given <code>name</code> on the
* provided {@link Object target object} to the supplied <code>value</code>.
* <p>
* This method traverses the class hierarchy in search of the desired field.
* In addition, an attempt will be made to make non-<code>public</code>
* fields <em>accessible</em>, thus allowing one to set
* <code>protected</code>, <code>private</code>, and
* <em>package-private</em> fields.
* Set the {@link Field field} with the given {@code name} on the provided
* {@link Object target object} to the supplied {@code value}.
*
* <p>This method traverses the class hierarchy in search of the desired
* field. In addition, an attempt will be made to make non-{@code public}
* fields <em>accessible</em>, thus allowing one to set {@code protected},
* {@code private}, and <em>package-private</em> fields.
*
* @param target the target object on which to set the field
* @param name the name of the field to set
* @param value the value to set
* @param type the type of the field (may be <code>null</code>)
* @param type the type of the field (may be {@code null})
* @see ReflectionUtils#findField(Class, String, Class)
* @see ReflectionUtils#makeAccessible(Field)
* @see ReflectionUtils#setField(Field, Object, Object)
@@ -102,9 +104,7 @@ public class ReflectionTestUtils {
public static void setField(Object target, String name, Object value, Class<?> type) {
Assert.notNull(target, "Target object must not be null");
Field field = ReflectionUtils.findField(target.getClass(), name, type);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + name + "] on target [" + target + "]");
}
Assert.notNull(field, "Could not find field [" + name + "] on target [" + target + "]");
if (logger.isDebugEnabled()) {
logger.debug("Setting field [" + name + "] on target [" + target + "]");
@@ -114,14 +114,12 @@ public class ReflectionTestUtils {
}
/**
* Get the field with the given <code>name</code> from the provided target
* object.
* <p>
* This method traverses the class hierarchy in search of the desired field.
* In addition, an attempt will be made to make non-<code>public</code>
* fields <em>accessible</em>, thus allowing one to get
* <code>protected</code>, <code>private</code>, and
* <em>package-private</em> fields.
* Get the field with the given {@code name} from the provided target object.
*
* <p>This method traverses the class hierarchy in search of the desired
* field. In addition, an attempt will be made to make non-{@code public}
* fields <em>accessible</em>, thus allowing one to get {@code protected},
* {@code private}, and <em>package-private</em> fields.
*
* @param target the target object on which to set the field
* @param name the name of the field to get
@@ -133,9 +131,7 @@ public class ReflectionTestUtils {
public static Object getField(Object target, String name) {
Assert.notNull(target, "Target object must not be null");
Field field = ReflectionUtils.findField(target.getClass(), name);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + name + "] on target [" + target + "]");
}
Assert.notNull(field, "Could not find field [" + name + "] on target [" + target + "]");
if (logger.isDebugEnabled()) {
logger.debug("Getting field [" + name + "] from target [" + target + "]");
@@ -145,17 +141,16 @@ public class ReflectionTestUtils {
}
/**
* Invoke the setter method with the given <code>name</code> on the supplied
* target object with the supplied <code>value</code>.
* <p>
* This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-
* <code>public</code> methods <em>accessible</em>, thus allowing one to
* invoke <code>protected</code>, <code>private</code>, and
* <em>package-private</em> setter methods.
* <p>
* In addition, this method supports JavaBean-style <em>property</em> names.
* For example, if you wish to set the <code>name</code> property on the
* Invoke the setter method with the given {@code name} on the supplied
* target object with the supplied {@code value}.
*
* <p>This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-{@code public}
* methods <em>accessible</em>, thus allowing one to invoke {@code protected},
* {@code private}, and <em>package-private</em> setter methods.
*
* <p>In addition, this method supports JavaBean-style <em>property</em>
* names. For example, if you wish to set the {@code name} property on the
* target object, you may pass either &quot;name&quot; or
* &quot;setName&quot; as the method name.
*
@@ -173,17 +168,16 @@ public class ReflectionTestUtils {
}
/**
* Invoke the setter method with the given <code>name</code> on the supplied
* target object with the supplied <code>value</code>.
* <p>
* This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-
* <code>public</code> methods <em>accessible</em>, thus allowing one to
* invoke <code>protected</code>, <code>private</code>, and
* <em>package-private</em> setter methods.
* <p>
* In addition, this method supports JavaBean-style <em>property</em> names.
* For example, if you wish to set the <code>name</code> property on the
* Invoke the setter method with the given {@code name} on the supplied
* target object with the supplied {@code value}.
*
* <p>This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-{@code public}
* methods <em>accessible</em>, thus allowing one to invoke {@code protected},
* {@code private}, and <em>package-private</em> setter methods.
*
* <p>In addition, this method supports JavaBean-style <em>property</em>
* names. For example, if you wish to set the {@code name} property on the
* target object, you may pass either &quot;name&quot; or
* &quot;setName&quot; as the method name.
*
@@ -199,7 +193,7 @@ public class ReflectionTestUtils {
*/
public static void invokeSetterMethod(Object target, String name, Object value, Class<?> type) {
Assert.notNull(target, "Target object must not be null");
Assert.notNull(name, "Method name must not be empty");
Assert.hasText(name, "Method name must not be empty");
Class<?>[] paramTypes = (type != null ? new Class<?>[] { type } : null);
String setterMethodName = name;
@@ -211,10 +205,8 @@ public class ReflectionTestUtils {
setterMethodName = name;
method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
}
if (method == null) {
throw new IllegalArgumentException("Could not find setter method [" + setterMethodName + "] on target ["
+ target + "] with parameter type [" + type + "]");
}
Assert.notNull(method, "Could not find setter method [" + setterMethodName + "] on target [" + target
+ "] with parameter type [" + type + "]");
if (logger.isDebugEnabled()) {
logger.debug("Invoking setter method [" + setterMethodName + "] on target [" + target + "]");
@@ -224,17 +216,16 @@ public class ReflectionTestUtils {
}
/**
* Invoke the getter method with the given <code>name</code> on the supplied
* target object with the supplied <code>value</code>.
* <p>
* This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-
* <code>public</code> methods <em>accessible</em>, thus allowing one to
* invoke <code>protected</code>, <code>private</code>, and
* <em>package-private</em> getter methods.
* <p>
* In addition, this method supports JavaBean-style <em>property</em> names.
* For example, if you wish to get the <code>name</code> property on the
* Invoke the getter method with the given {@code name} on the supplied
* target object with the supplied {@code value}.
*
* <p>This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-{@code public}
* methods <em>accessible</em>, thus allowing one to invoke {@code protected},
* {@code private}, and <em>package-private</em> getter methods.
*
* <p>In addition, this method supports JavaBean-style <em>property</em>
* names. For example, if you wish to get the {@code name} property on the
* target object, you may pass either &quot;name&quot; or
* &quot;getName&quot; as the method name.
*
@@ -249,7 +240,7 @@ public class ReflectionTestUtils {
*/
public static Object invokeGetterMethod(Object target, String name) {
Assert.notNull(target, "Target object must not be null");
Assert.notNull(name, "Method name must not be empty");
Assert.hasText(name, "Method name must not be empty");
String getterMethodName = name;
if (!name.startsWith(GETTER_PREFIX)) {
@@ -260,10 +251,7 @@ public class ReflectionTestUtils {
getterMethodName = name;
method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
}
if (method == null) {
throw new IllegalArgumentException("Could not find getter method [" + getterMethodName + "] on target ["
+ target + "]");
}
Assert.notNull(method, "Could not find getter method [" + getterMethodName + "] on target [" + target + "]");
if (logger.isDebugEnabled()) {
logger.debug("Invoking getter method [" + getterMethodName + "] on target [" + target + "]");
@@ -272,4 +260,48 @@ public class ReflectionTestUtils {
return ReflectionUtils.invokeMethod(method, target);
}
/**
* Invoke the method with the given {@code name} on the supplied target
* object with the supplied arguments.
*
* <p>This method traverses the class hierarchy in search of the desired
* method. In addition, an attempt will be made to make non-{@code public}
* methods <em>accessible</em>, thus allowing one to invoke {@code protected},
* {@code private}, and <em>package-private</em> methods.
*
* @param target the target object on which to invoke the specified method
* @param name the name of the method to invoke
* @param args the arguments to provide to the method
* @return the invocation result, if any
* @see MethodInvoker
* @see ReflectionUtils#makeAccessible(Method)
* @see ReflectionUtils#invokeMethod(Method, Object, Object[])
* @see ReflectionUtils#handleReflectionException(Exception)
*/
@SuppressWarnings("unchecked")
public static <T> T invokeMethod(Object target, String name, Object... args) {
Assert.notNull(target, "Target object must not be null");
Assert.hasText(name, "Method name must not be empty");
try {
MethodInvoker methodInvoker = new MethodInvoker();
methodInvoker.setTargetObject(target);
methodInvoker.setTargetMethod(name);
methodInvoker.setArguments(args);
methodInvoker.prepare();
if (logger.isDebugEnabled()) {
logger.debug("Invoking method [" + name + "] on target [" + target + "] with arguments ["
+ ObjectUtils.nullSafeToString(args) + "]");
}
return (T) methodInvoker.invoke();
}
catch (Exception e) {
ReflectionUtils.handleReflectionException(e);
}
throw new IllegalStateException("Should never get here");
}
}