iT邦幫忙

0

應用系統建置前準備工具 - ReflectUtils 反射工具

  • 分享至 

  • xImage
  •  

ReflectUtils(反射工具)

概述

ReflectUtils 是一個專案中常用的反射輔助類,將常見的 Java 反射操作封裝為簡潔的靜態方法,方便:

  • 直接讀寫私有/受保護欄位(不經 getter/setter)
  • 動態呼叫方法(支持完整簽章或僅以名稱匹配)
  • 取得父類宣告的泛型型別
  • 處理 CGLIB 代理類別還原原始類別
  • 將受檢反射例外包成非受檢例外以簡化呼叫端

專案相關程式

  • TypeConvert(參數型別轉換)
  • DateUtils(字串 -> Date 轉換)

第三方元件 (Dependency)

  • org.apache.commons.lang3;
  • lombok.extern.slf4j.Slf4j;

主要功能(方法與簡短範例)

下列範例對應 src/main/java/.../ReflectUtils.java 中公開方法。

1. 動態讀取 getter(支援多層)

// 取值 a.b.c 的最終值
String value = ReflectUtils.invokeGetter(obj, "a.b.c");

方法簽章:

public static <E> E invokeGetter(Object obj, String propertyName)

2. 動態呼叫 setter(支援多層,會嘗試型別轉換)

ReflectUtils.invokeSetter(obj, "address.street", "Main St");

方法簽章:

public static <E> void invokeSetter(Object obj, String propertyName, E value)

3. 直接讀寫欄位(跳過存取修飾)

// 讀取私有欄位
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

4. 以完整簽章或僅名稱呼叫方法

// 指定參數型別呼叫
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 等)。

5. 取得可訪問的 Field/Method

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)

6. 取得父類宣告的泛型型別

Class<?> generic = ReflectUtils.getClassGenericType(MyDao.class);

方法簽章:

public static <T> Class<T> getClassGenericType(Class clazz)
public static Class getClassGenericType(Class clazz, int index)

7. 處理 CGLIB 代理類別

Class<?> userClass = ReflectUtils.getUserClass(proxyObject);

方法簽章:

public static Class<?> getUserClass(Object instance)

8. 例外轉換工具

throw ReflectUtils.convertReflectionExceptionToUnchecked("msg", ex);

方法簽章:

public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e)

重要注意事項

  1. 安全性

    • 多數方法會呼叫 setAccessible(true),在有嚴格安全設定或 SecurityManager 的環境中可能被限制;請避免在不信任的輸入上使用。
  2. 性能

    • 反射呼叫成本高於直接呼叫,若需重複使用建議先取得 Method/Field 實例後重複使用。
  3. 型別轉換限制

    • invokeMethodByName 進行有限的型別轉換(使用 TypeConvertDateUtils),若轉換失敗會產生例外。
  4. 泛型與代理

    • getClassGenericType 僅解析父類直接聲明的泛型;介面或多層複雜泛型情況可能無法正確解析。
  5. 日誌與錯誤處理

    • 工具類使用 SLF4J 記錄 debug/error,呼叫端應根據業務需求捕捉並處理拋出的 RuntimeException。

    單元測試範例

    這些測試案例展示了以下功能的正確性:

    • 欄位讀寫操作
    • 方法動態呼叫
    • 泛型型別處理
    • 存取控制處理
    • 例外轉換機制

    1. 欄位值存取測試

    @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());
       }
    }
    

    2. Getter/Setter 動態呼叫測試

    @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"));
    }
    

    3. 方法動態呼叫測試

    @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 }));
    }
    

    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));
    }
    

    5. 例外轉換測試

    @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);
    }
    

    測試案例說明

    1. 欄位值存取測試

      • 驗證私有欄位的讀取
      • 測試公開欄位的存取
      • 確保型別轉換正確
    2. Getter/Setter 測試

      • 測試巢狀物件存取
      • 驗證多層級屬性處理
      • 確認值的正確傳遞
    3. 方法動態呼叫

      • 測試私有方法呼叫
      • 驗證參數傳遞
      • 確認回傳值處理
    4. 泛型處理

      • 測試單一泛型參數
      • 驗證多重泛型參數
      • 確認邊界情況處理
    5. 例外處理

      • 測試各種反射例外轉換
      • 驗證例外訊息保留
      • 確認例外型別轉換

    其他注意事項

    1. 測試案例設計

      • 使用巢狀類別隔離測試環境
      • 涵蓋各種存取修飾符情況
      • 包含正常與異常情況測試
    2. 安全性考量

      • 測試私有成員存取限制
      • 驗證存取控制規則
      • 確保例外處理完整性
    3. 效能影響

      • 注意反射操作的效能開銷
      • 考慮快取反射資訊
      • 適當處理重複操作

程式碼 ReflectUtils.java

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;
    }
}

單元測試程式碼 ReflectUtilsTest.java

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);
    }

}


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言