ReflectUtils 是一個專案中常用的反射輔助類,將常見的 Java 反射操作封裝為簡潔的靜態方法,方便:
TypeConvert(參數型別轉換)DateUtils(字串 -> Date 轉換)下列範例對應 src/main/java/.../ReflectUtils.java 中公開方法。
// 取值 a.b.c 的最終值
String value = ReflectUtils.invokeGetter(obj, "a.b.c");
方法簽章:
public static <E> E invokeGetter(Object obj, String propertyName)
ReflectUtils.invokeSetter(obj, "address.street", "Main St");
方法簽章:
public static <E> void invokeSetter(Object obj, String propertyName, E value)
// 讀取私有欄位
String name = ReflectUtils.getFieldValue(person, "name");
// 設定私有欄位
ReflectUtils.setFieldValue(person, "name", "Alice");
方法簽章:
public static <E> E getFieldValue(Object obj, String fieldName) throws NoSuchFieldException
public static <E> void setFieldValue(Object obj, String fieldName, E value) throws NoSuchFieldException, IllegalAccessException
// 指定參數型別呼叫
Integer sum = ReflectUtils.invokeMethod(calc, "add", new Class[]{Integer.class,Integer.class}, new Object[]{1,2});
// 只以名稱呼叫,會嘗試型別轉換
Integer result = ReflectUtils.invokeMethodByName(calc, "add", new Object[]{"2", "3"});
方法簽章:
public static <E> E invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args)
public static <E> E invokeMethodByName(Object obj, String methodName, Object[] args)
注意:invokeMethodByName 會嘗試將傳入參數轉為目標方法所需型別(支援 String->Number/Boolean、String->Date 等)。
Field f = ReflectUtils.getAccessibleField(obj, "id");
Method m = ReflectUtils.getAccessibleMethod(obj, "doSomething", String.class);
方法簽章:
public static Field getAccessibleField(Object obj, String fieldName)
public static Method getAccessibleMethod(Object obj, String methodName, Class<?>... parameterTypes)
public static Method getAccessibleMethodByName(Object obj, String methodName, int argsNum)
Class<?> generic = ReflectUtils.getClassGenericType(MyDao.class);
方法簽章:
public static <T> Class<T> getClassGenericType(Class clazz)
public static Class getClassGenericType(Class clazz, int index)
Class<?> userClass = ReflectUtils.getUserClass(proxyObject);
方法簽章:
public static Class<?> getUserClass(Object instance)
throw ReflectUtils.convertReflectionExceptionToUnchecked("msg", ex);
方法簽章:
public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)
安全性
setAccessible(true),在有嚴格安全設定或 SecurityManager 的環境中可能被限制;請避免在不信任的輸入上使用。性能
Method/Field 實例後重複使用。型別轉換限制
invokeMethodByName 進行有限的型別轉換(使用 TypeConvert、DateUtils),若轉換失敗會產生例外。泛型與代理
getClassGenericType 僅解析父類直接聲明的泛型;介面或多層複雜泛型情況可能無法正確解析。日誌與錯誤處理
這些測試案例展示了以下功能的正確性:
@Test
public void testGetFieldValue() {
class TestClass {
private String field1 = "value1";
public int field2 = 42;
}
TestClass obj = new TestClass();
try {
assertEquals("value1", ReflectUtils.getFieldValue(obj, "field1"));
assertEquals(42, (int) ReflectUtils.getFieldValue(obj, "field2"));
} catch (Exception e) {
fail("Exception should not be thrown: " + e.getMessage());
}
}
@Test
public void testInvokeGetterAndSetter() {
class Inner {
private String value = "abc";
public String getValue() { return value; }
public void setValue(String v) { value = v; }
}
class TestClass {
private Inner inner = new Inner();
public Inner getInner() { return inner; }
public void setInner(Inner i) { inner = i; }
}
TestClass obj = new TestClass();
assertEquals("abc", ReflectUtils.invokeGetter(obj, "inner.value"));
ReflectUtils.invokeSetter(obj, "inner.value", "xyz");
assertEquals("xyz", ReflectUtils.invokeGetter(obj, "inner.value"));
}
@Test
public void testInvokeMethod() {
class TestClass {
private String echo(String s) {
return "Echo:" + s;
}
public int add(int a, int b) {
return a + b;
}
}
TestClass obj = new TestClass();
assertEquals("Echo:hello",
ReflectUtils.invokeMethod(obj, "echo",
new Class[] { String.class },
new Object[] { "hello" }));
assertEquals(7, (int)
ReflectUtils.invokeMethod(obj, "add",
new Class[] { int.class, int.class },
new Object[] { 3, 4 }));
}
@Test
public void testGetClassGenericType() {
class Base<T> {}
class Sub extends Base<String> {}
assertEquals(String.class,
ReflectUtils.getClassGenericType(Sub.class));
assertEquals(Object.class,
ReflectUtils.getClassGenericType(Object.class));
}
@Test
public void testGetClassGenericTypeWithIndex() {
class Base<A, B> {}
class Sub extends Base<Integer, Double> {}
assertEquals(Integer.class,
ReflectUtils.getClassGenericType(Sub.class, 0));
assertEquals(Double.class,
ReflectUtils.getClassGenericType(Sub.class, 1));
}
@Test
public void testConvertReflectionExceptionToUnchecked() {
Exception e1 = new IllegalAccessException("fail");
Exception e2 = new NoSuchMethodException("fail");
RuntimeException ex1 = ReflectUtils.convertReflectionExceptionToUnchecked(
"msg", e1);
RuntimeException ex2 = ReflectUtils.convertReflectionExceptionToUnchecked(
"msg", e2);
assertTrue(ex1 instanceof IllegalArgumentException);
assertTrue(ex2 instanceof IllegalArgumentException);
}
欄位值存取測試
Getter/Setter 測試
方法動態呼叫
泛型處理
例外處理
測試案例設計
安全性考量
效能影響
package tw.lewishome.webapp.base.utility.common;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.util.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
/**
* 反射(Reflect)工具類,提供多種基於 Java 反射機制的操作方法。
*
* 支援直接存取物件的私有或保護屬性、方法,並可動態調用 getter/setter、方法、讀寫屬性值等。
* 亦可取得泛型類型、處理 CGLIB 代理類型、將反射異常轉換為非檢查型異常等。
*
* <ul>
* <li>invokeGetter:動態調用物件的 getter 方法,支援多層級屬性(如 a.b.c)。</li>
* <li>invokeSetter:動態調用物件的 setter 方法,支援多層級屬性。</li>
* <li>getFieldValue / setFieldValue:直接讀取或設定物件屬性值,無視 private/protected 修飾符,不經
* getter/setter。</li>
* <li>invokeMethod / invokeMethodByName:直接執行物件方法,無視 private/protected
* 修飾符,支援參數型別自動轉換。</li>
* <li>getAccessibleField / getAccessibleMethod /
* getAccessibleMethodByName:循環向上型別取得可訪問的屬性或方法。</li>
* <li>makeAccessible:將 private/protected 屬性或方法設為 public 可訪問。</li>
* <li>getClassGenericType:取得類別定義中父類的泛型參數型別。</li>
* <li>getUserClass:取得原始類型(處理 CGLIB 代理類)。</li>
* <li>convertReflectionExceptionToUnchecked:將反射相關 checked exception 轉換為
* unchecked exception。</li>
* </ul>
*
* 注意事項:
* <ul>
* <li>本工具類多數方法會略過 Java 的存取修飾符限制,請謹慎使用於安全性敏感場景。</li>
* <li>泛型型別取得僅支援父類型定義於 class 宣告處。</li>
* <li>方法調用時會自動嘗試型別轉換(如 String 轉 Integer),但仍需留意型別正確性。</li>
* </ul>
*
* 典型用途:
* <ul>
* <li>框架開發、ORM、動態資料綁定、測試工具等需動態存取物件屬性或方法場景。</li>
* </ul>
*
* @author Lewis
* @since 1.0
*/
@SuppressWarnings("rawtypes")
@Slf4j
public class ReflectUtils {
// Private constructor to prevent instantiation
private ReflectUtils() {
throw new IllegalStateException("This is a utility class and cannot be instantiated");
}
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
/**
* 調用Getter方法.
* 支持多級,如:對象名.對象名.方法
*
* @param obj a {@link java.lang.Object} object
* @param propertyName a String object
* @param <E> a E class
* @return a E object
*/
@SuppressWarnings("unchecked")
public static <E> E invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
return (E) object;
}
/**
* 調用Setter方法, 僅匹配方法名。
* 支持多級,如:對象名.對象名.方法
*
* @param obj a {@link java.lang.Object} object
* @param propertyName a String object
* @param value a E object
* @param <E> a E class
*/
public static <E> void invokeSetter(Object obj, String propertyName, E value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i = 0; i < names.length; i++) {
if (i < names.length - 1) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
} else {
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}
/**
* 直接讀取物件變數值, 無視private/protected修飾符, 不經過getter函數.
*
* @param obj a {@link java.lang.Object} object
* @param fieldName a String object
* @param <E> a E class
* @return a E object
* @throws SecurityException SecurityException
* @throws NoSuchFieldException NoSuchFieldException
*/
@SuppressWarnings("unchecked")
public static <E> E getFieldValue(final Object obj, final String fieldName)
throws NoSuchFieldException, SecurityException {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
log.debug("在 [ {} ] 中,沒有找到 [ {} ] 變數 ", obj.getClass(), fieldName);
return null;
}
E result = null;
try {
result = (E) field.get(obj);
} catch (IllegalAccessException e) {
log.error("不可能拋出的異常{}", e.getMessage());
}
return result;
}
/**
* 直接設置物件變數值, 無視private/protected修飾符, 不經過setter函數.
*
* @param obj a {@link java.lang.Object} object
* @param fieldName a String object
* @param value a E object
* @param <E> a E class
* @throws SecurityException SecurityException
* @throws NoSuchFieldException NoSuchFieldException
* @throws IllegalAccessException IllegalAccessException
* @throws IllegalArgumentException IllegalArgumentException
*/
public static <E> void setFieldValue(final Object obj, final String fieldName, final E value)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
log.debug("在 [ {} ] 中,沒有找到 [ {} ] 變數 ", obj.getClass(), fieldName);
return;
}
field.set(obj, value);
// ReflectUtils.setFieldValue(obj, fieldName, value);
}
/**
* 直接執行物件內Method方法, 無視private/protected修飾符.
* 用於一次性調用的情況,否則應使用getAccessibleMethod()函數獲得Method後重複使用.
* 需要傳入方法名 + 參數類型,
*
* @param obj a {@link java.lang.Object} object
* @param methodName a String object
* @param parameterTypes an array of {@link java.lang.Class} objects
* @param args an array of {@link java.lang.Object} objects
* @param <E> a E class
* @return a E object
*/
@SuppressWarnings("unchecked")
public static <E> E invokeMethod( Object obj, String methodName, Class<?>[] parameterTypes,
Object[] args) {
if (obj == null || methodName == null) {
return null;
}
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
log.debug("在 [ {} ] 中,沒有找到 [ {} ] 方法 ", obj.getClass(), methodName);
return null;
}
try {
return (E) method.invoke(obj, args);
} catch (Exception e) {
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
throw convertReflectionExceptionToUnchecked(msg, e);
}
}
/**
* 直接執行物件內Method方法, 無視private/protected修飾符,
* 用於一次性調用的情況,否則應使用getAccessibleMethod()函數獲得Method後重複使用.
* 只需要傳入方法名,如果有多個同名函數執行第一個 但注意參數。
*
* @param obj a {@link java.lang.Object} object
* @param methodName a String object
* @param args an array of {@link java.lang.Object} objects
* @param <E> a E class
* @return a E object
*/
@SuppressWarnings("unchecked")
public static <E> E invokeMethodByName( Object obj, String methodName, Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName, args.length);
if (method == null) {
// 如果為空不報錯,直接返回空。
log.debug("在 [ {} ] 中,沒有找到 [ {} ] 方法 ", obj.getClass(), methodName);
return null;
}
try {
// 物件類型轉換(將參數資料類型轉換為目標方法參數類型)
Class<?>[] cs = method.getParameterTypes();
for (int i = 0; i < cs.length; i++) {
if (args[i] != null && !args[i].getClass().equals(cs[i])) {
if (cs[i] == String.class) {
args[i] = args[i].toString();
if (StringUtils.endsWith((String) args[i], ".0")) {
args[i] = StringUtils.substringBefore((String) args[i], ".0");
}
} else if (cs[i] == Integer.class) {
args[i] = TypeConvert.toInteger(args[i]);
} else if (cs[i] == Long.class) {
args[i] = TypeConvert.toLong(args[i]);
} else if (cs[i] == Double.class) {
args[i] = TypeConvert.toDouble(args[i]);
} else if (cs[i] == Float.class) {
args[i] = TypeConvert.toFloat(args[i]);
} else if (cs[i] == Date.class) {
if (args[i] instanceof String) {
args[i] = DateUtils.objectDateToDate(args[i], DateUtils.YYYY_MM_DD_UNDER);
} // else if (args[i] instanceof Double) {
// args[i] = DateUtil.getJavaDate((Double) args[i]);
// }
} else if (cs[i] == boolean.class || cs[i] == Boolean.class) {
args[i] = TypeConvert.toBoolean(args[i]);
}
}
}
return (E) method.invoke(obj, args);
} catch (Exception e) {
String msg = "method: " + method + ", obj: " + obj + ", args: " + args + "";
throw convertReflectionExceptionToUnchecked(msg, e);
}
}
/**
* 循環向上轉型, 獲取對象的DeclaredField, 並強制設置為可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
*
* @param obj a {@link java.lang.Object} object
* @param fieldName a String object
* @return a {@link java.lang.reflect.Field} object
* @throws SecurityException SecurityException
* @throws NoSuchFieldException NoSuchFieldException
*/
public static Field getAccessibleField( Object obj, String fieldName)
throws NoSuchFieldException, SecurityException {
// 為空不報錯。直接返回 null
if (obj == null) {
return null;
}
Field field = null;
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
field = superClass.getDeclaredField(fieldName);
field.setAccessible(true);
// makeAccessible(field);
}
return field;
}
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置為可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 需要傳入方法名+參數類型。
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*
* @param obj a {@link java.lang.Object} object
* @param methodName a String object
* @param parameterTypes a {@link java.lang.Class} object
* @return a {@link java.lang.reflect.Method} object
*/
public static Method getAccessibleMethod( Object obj, String methodName,
Class<?>... parameterTypes) {
// 為空不報錯。直接返回 null
if (obj == null) {
return null;
}
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType
.getSuperclass()) {
try {
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
} catch (NoSuchMethodException e) {
continue;
}
}
return null;
}
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置為可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 只需要傳入方法名。
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*
* @param obj a {@link java.lang.Object} object
* @param methodName a String object
* @param argsNum a int
* @return a {@link java.lang.reflect.Method} object
*/
public static Method getAccessibleMethodByName( Object obj, String methodName, int argsNum) {
// 為空不報錯。直接返回 null
if (obj == null) {
return null;
}
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType
.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 改變private/protected的方法為public,盡量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*
* @param method a {@link java.lang.reflect.Method} object
*/
public static void makeAccessible(Method method) {
if (method == null) {
return;
}
method.setAccessible(true);
// if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
// && !method.canAccess(method.getDeclaringClass())) {
// method.setAccessible(true);
// // ReflectionUtils.makeAccessible(method);
// }
}
/**
* 改變private/protected的成員變量為public,盡量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*
* @param field a {@link java.lang.reflect.Field} object
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
|| Modifier.isFinal(field.getModifiers()))
&& !field.canAccess(field.getDeclaringClass().getModifiers())) {
ReflectionUtils.makeAccessible(field);
// field.setAccessible(true);
}
}
/**
* 通過代理, 獲得Class定義中聲明的泛型參數的類型, 註意泛型必須定義在父類處
* 如無法找到, 返回Object.class.
*
* @param clazz a {@link java.lang.Class} object
* @param <T> a T class
* @return a {@link java.lang.Class} object
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenericType( Class clazz) {
return getClassGenericType(clazz, 0);
}
/**
* 通過代理, 獲得Class定義中聲明的父類的泛型參數的類型.
* 如無法找到, 返回Object.class.
*
* @param clazz a {@link java.lang.Class} object
* @param index a int
* @return a {@link java.lang.Class} object
*/
public static Class getClassGenericType( Class clazz, int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
log.debug("{}'s superclass not ParameterizedType", clazz.getSimpleName());
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
log.debug("Index: {} , Size of {} 's Parameterized Type: {} ", index, clazz.getSimpleName(),
params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
log.debug("{} not set the actual class on superclass generic parameter", clazz.getSimpleName());
return Object.class;
}
return (Class) params[index];
}
/**
*
* getUserClass.
*
*
* @param instance a {@link java.lang.Object} object
* @return a {@link java.lang.Class} object
*/
public static Class<?> getUserClass(Object instance) {
if (instance == null) {
throw new RuntimeException("Instance must not be null");
}
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
/**
* 將代理時的checked exception轉換為unchecked exception.
*
* @param msg a String object
* @param e a {@link java.lang.Exception} object
* @return a {@link java.lang.RuntimeException} object
*/
public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) {
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException) {
return new IllegalArgumentException(msg, e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
}
return new RuntimeException(msg, e);
}
/**
* Get all fields of one class and its static inner class, include
* private/protected/default/public, but not include parent class.
*
* @param clazz a {@link java.lang.Class} object
* @return a List object
*/
public static List<Field> getAllClassFields(Class<?> clazz) {
List<Field> rtnClassFields = new ArrayList<>();
// 取得 public 欄位,包括繼承自父類的 public 欄位
List<Field> allClassFields = getOneClassFields(clazz);
for (Field oneClassFields : allClassFields) {
try {
if (StringUtils.containsIgnoreCase(oneClassFields.getType().getName(), "$")) {
Class<?> staticInnerClass = Class.forName(oneClassFields.getType().getName());
List<Field> allInnerFields = getOneClassFields(staticInnerClass);
for (Field oneInnerField : allInnerFields) {
rtnClassFields.add(oneInnerField);
}
} else {
rtnClassFields.add(oneClassFields);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return rtnClassFields;
}
/**
* Get all fields of one class, include private/protected/default/public, but
* not include parent class.
*
* @param clazz a {@link java.lang.Class} object
* @return a List object
*/
public static List<Field> getOneClassFields(Class<?> clazz) {
/** 取得 public 欄位,包括繼承自父類的 public 欄位 */
List<Field> allClassFields = new ArrayList<>();
while (clazz != null) {
allClassFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
return allClassFields;
}
}
package tw.lewishome.webapp.base.utility.common;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@SuppressWarnings("unused")
public class ReflectUtilsTest {
private String field1G = "value1";
@Test
public void testGetFieldValue() {
class TestClass {
private String field1 = "value1";
public int field2 = 42;
}
TestClass obj = new TestClass();
try {
assertEquals("value1", ReflectUtils.getFieldValue(obj, "field1"));
assertEquals(42, (int) ReflectUtils.getFieldValue(obj, "field2"));
} catch (Exception e) {
fail("Exception should not be thrown: " + e.getMessage());
}
}
@Test
public void testSetFieldValue() {
class TestClass {
private String field1 = "init";
public int field2 = 0;
}
try {
TestClass obj = new TestClass();
ReflectUtils.setFieldValue(obj, "field1", "changed");
ReflectUtils.setFieldValue(obj, "field2", 99);
assertEquals("changed", ReflectUtils.getFieldValue(obj, "field1"));
assertEquals(99, (int) ReflectUtils.getFieldValue(obj, "field2"));
} catch (Exception e) {
fail("Exception should not be thrown: " + e.getMessage());
}
}
@Test
public void testInvokeGetterAndSetter() {
class Inner {
private String value = "abc";
public String getValue() {
return value;
}
public void setValue(String v) {
value = v;
}
}
class TestClass {
private Inner inner = new Inner();
public Inner getInner() {
return inner;
}
public void setInner(Inner i) {
inner = i;
}
}
TestClass obj = new TestClass();
assertEquals("abc", ReflectUtils.invokeGetter(obj, "inner.value"));
ReflectUtils.invokeSetter(obj, "inner.value", "xyz");
assertEquals("xyz", ReflectUtils.invokeGetter(obj, "inner.value"));
}
@Test
public void testInvokeMethod() {
class TestClass {
private String echo(String s) {
return "Echo:" + s;
}
public int add(int a, int b) {
return a + b;
}
}
TestClass obj = new TestClass();
assertEquals("Echo:hello",
ReflectUtils.invokeMethod(obj, "echo", new Class[] { String.class }, new Object[] { "hello" }));
assertEquals(7, (int)
ReflectUtils.invokeMethod(obj, "add", new Class[] { int.class, int.class }, new Object[] { 3, 4 }));
}
@Test
public void testInvokeMethodByName() {
class TestClass {
private String concat(String a, String b) {
return a + b;
}
public int mul(int a, int b) {
return a * b;
}
}
TestClass obj = new TestClass();
assertEquals("ab", ReflectUtils.invokeMethodByName(obj, "concat", new Object[] { "a", "b" }));
assertEquals(12, (int) ReflectUtils.invokeMethodByName(obj, "mul", new Object[] { 3, 4 }));
}
@Test
public void testGetAccessibleField() throws Exception {
class TestClass {
private String secret = "hidden";
}
TestClass obj = new TestClass();
Field field = ReflectUtils.getAccessibleField(obj, "secret");
assertNotNull(field);
assertEquals("secret", field.getName());
assertEquals("hidden", field.get(obj));
}
@Test
public void testGetAccessibleMethod() throws Exception {
class TestClass {
private String greet(String name) {
return "Hello " + name;
}
}
TestClass obj = new TestClass();
Method method = ReflectUtils.getAccessibleMethod(obj, "greet", String.class);
assertNotNull(method);
assertEquals("greet", method.getName());
assertEquals("Hello John", method.invoke(obj, "John"));
}
@Test
public void testGetAccessibleMethodByName() throws Exception {
class TestClass {
private String foo(int x) {
return "foo" + x;
}
}
TestClass obj = new TestClass();
Method method = ReflectUtils.getAccessibleMethodByName(obj, "foo", 1);
assertNotNull(method);
assertEquals("foo", method.getName());
assertEquals("foo5", method.invoke(obj, 5));
}
@Test
public void testGetClassGenericType() {
class Base<T> {
}
class Sub extends Base<String> {
}
assertEquals(String.class, ReflectUtils.getClassGenericType(Sub.class));
assertEquals(Object.class, ReflectUtils.getClassGenericType(Object.class));
}
@Test
public void testGetClassGenericTypeWithIndex() {
class Base<A, B> {
}
class Sub extends Base<Integer, Double> {
}
assertEquals(Integer.class, ReflectUtils.getClassGenericType(Sub.class, 0));
assertEquals(Double.class, ReflectUtils.getClassGenericType(Sub.class, 1));
assertEquals(Object.class, ReflectUtils.getClassGenericType(Sub.class, 2));
}
@Test
public void testGetUserClass() {
class TestClass {
}
TestClass obj = new TestClass();
assertEquals(TestClass.class, ReflectUtils.getUserClass(obj));
}
@Test
public void testConvertReflectionExceptionToUnchecked() {
Exception e1 = new IllegalAccessException("fail");
Exception e2 = new IllegalArgumentException("fail");
Exception e3 = new NoSuchMethodException("fail");
Exception e4 = new Exception("fail");
RuntimeException ex1 = ReflectUtils.convertReflectionExceptionToUnchecked("msg", e1);
RuntimeException ex2 = ReflectUtils.convertReflectionExceptionToUnchecked("msg", e2);
RuntimeException ex3 = ReflectUtils.convertReflectionExceptionToUnchecked("msg", e3);
RuntimeException ex4 = ReflectUtils.convertReflectionExceptionToUnchecked("msg", e4);
assertTrue(ex1 instanceof IllegalArgumentException);
assertTrue(ex2 instanceof IllegalArgumentException);
assertTrue(ex3 instanceof IllegalArgumentException);
assertTrue(ex4 instanceof RuntimeException);
}
}