# Reflect 應用

#### 從 Class 獲取資訊

Class 物件表示所載入的類別，取得Class物件之後，你就可以取得與類別相關聯的資訊，像是套件（package）（別忘了package也是類別名稱的一部 份）、建構方法、方法成員、資料成員等的訊息，而每一個訊息，也會有相應的類別型態，例如套件的對應型態是`java.lang.Package`，建構方法的對應型態是`java.lang.reflect.Constructor`，方法成員的對應型態是`java.lang.reflect.Method`，資料成員的對應型態是`java.lang.reflect.Field`等。\
\
\
來看個簡單的示範，以下可以讓您取得所指定類別上的套件名稱：

```java
try {
	Class c = Class.forName(args[0]);
	Package p = c.getPackage();
	System.out.println(p.getName());
} catch (ArrayIndexOutOfBoundsException e) {
	System.out.println("沒有指定類別");
} catch (ClassNotFoundException e) {
	System.out.println("找不到指定類別");
}
```

你可以分別取回`Field、Constructor、Method`等物件，分別代表資料成員、建構方法與方法成員，以下範例簡單地實作了取得類別基本資訊的程式：

```java
try {
	Class c = Class.forName(args[0]);
	// 取得套件代表物件
	Package p = c.getPackage();

	System.out.printf("package %s;%n", p.getName());

	// 取得型態修飾，像是class、interface
	int m = c.getModifiers();

	System.out.print(Modifier.toString(m) + " ");
	// 如果是介面
	if (Modifier.isInterface(m)) {
		System.out.print("interface ");
	} else {
		System.out.print("class ");
	}

	System.out.println(c.getName() + " {");

	// 取得宣告的資料成員代表物件
	Field[] fields = c.getDeclaredFields();
	for (Field field : fields) {
		// 顯示權限修飾，像是public、protected、private
		System.out.print("\t" + Modifier.toString(field.getModifiers()));
		// 顯示型態名稱
		System.out.print(" " + field.getType().getName() + " ");
		// 顯示資料成員名稱
		System.out.println(field.getName() + ";");
	}

	// 取得宣告的建構方法代表物件
	Constructor[] constructors = c.getDeclaredConstructors();
	for (Constructor constructor : constructors) {
		// 顯示權限修飾，像是public、protected、private
		System.out.print("\t" + Modifier.toString(constructor.getModifiers()));
		// 顯示建構方法名稱
		System.out.println(" " + constructor.getName() + "();");
	}
	// 取得宣告的方法成員代表物件
	Method[] methods = c.getDeclaredMethods();
	for (Method method : methods) {
		// 顯示權限修飾，像是public、protected、private
		System.out.print("\t" + Modifier.toString(method.getModifiers()));
		// 顯示返回值型態名稱
		System.out.print(" " + method.getReturnType().getName() + " ");
		// 顯示方法名稱
		System.out.println(method.getName() + "();");
	}
	System.out.println("}");
} catch (ArrayIndexOutOfBoundsException e) {
	System.out.println("沒有指定類別");
} catch (ClassNotFoundException e) {
	System.out.println("找不到指定類別");
}
```

#### 亦可呼叫物件內方法

看看以下範例以最簡單的`Get/Set`方法

```java
public static void reflectTest() {
	try {
		Class c = new AccountingReport().getClass();
		Object stockObj = c.getDeclaredConstructor().newInstance();

		// 呼叫Set方法
		Method[] methods = c.getMethods();
		for (Method method : methods) {
			if ("setCode".equals(method.getName()))
				method.invoke(stockObj, "1100");
			else if("setName".equals(method.getName()))
				method.invoke(stockObj, "現金及約當現金");
		}

		System.out.println(stockObj);

		// 呼叫Set方法
		for (Method method : methods) {
			if ("getCode".equals(method.getName()))
				System.out.println("get code value:" + method.invoke(stockObj));
			else if("getName".equals(method.getName()))
				System.out.println("get name value:" + method.invoke(stockObj));
		}
	} catch (ArrayIndexOutOfBoundsException e) {
		System.out.println("沒有指定類別");
	} catch (Exception e) {
		e.printStackTrace();
		System.out.println("找不到指定類別");
	}
}
```

#### 進階應用 - auto get/set value

```java
/**
 * NamingUtils 此物件為將變數名稱駝峰命名
 * 將含底線字串轉換為首字母小寫駝峰命名
 * Ex. set_code => setCode
 */

public static <T> T autoSetValue(T target, String fieldName, Object data){
	try {
		Class c = target.getClass();
		Method[] methods = c.getMethods();
		String setMethodName = NamingUtils.camelize("set_" + fieldName);
		for(Method method : methods){
			if(setMethodName.equals(method.getName())){
				method.invoke(target,data);
			}
		}
		return target;
	} catch (ArrayIndexOutOfBoundsException e) {
		System.out.println("沒有指定類別");
	} catch (Exception e) {
		System.out.println("找不到指定類別");
	}
	return target;
}

public static <T> Optional<Object> autoGetValue(T source, String fieldName) {
	try {
		Class c = source.getClass();
		Method[] methods = c.getMethods();
		String getMethodName = NamingUtils.camelize("get_" + fieldName);
		for (Method method : methods) {
			if (getMethodName.equals(method.getName()))
				return Optional.of(method.invoke(source));
		}
	} catch (ArrayIndexOutOfBoundsException e) {
		System.out.println("沒有指定類別");
	} catch (Exception e) {
		System.out.println("找不到指定類別");
	}
	return Optional.empty();
}
```

### 實戰案例

需求內容:&#x20;

1. 輸出前後資料比對結果
2. 轉換變數名稱，改為可閱讀內容 Ex. status -> 狀態
3. 可轉換變數資料結果 Ex. 1:啟用, 0: 停用 -> status:1 -> 狀態:啟用
4. 排序固定，方便查看比對內容

廢話不多，先看結果。

{% tabs %}
{% tab title="主程式" %}

```java
public class BeanUtilTest_ {
    public static void main(String[] args) {
        DemoBean bean = new DemoBean().setDate(new Date(System.currentTimeMillis() - 60000l))
                .setType("A")
                .setInteger(158)
                .setBigDecimal(BigDecimal.valueOf(177l))
                .setStatus(CommonOnOffStatus.ON);

        DemoBean bean2 = new DemoBean().setDate(new Date())
                .setType("B")
                .setInteger(200)
                .setBigDecimal(BigDecimal.valueOf(307l))
                .setStatus(CommonOnOffStatus.OFF);

        Map<String, FieldChange> compareResultMap = BeanUtils.compare(bean, bean2);

        Map<String, Function> convertFunctionMap = new HashMap<>();
        convertFunctionMap.put("status", (Function<CommonOnOffStatus, String>) o -> Objects.nonNull(o) ? o.getExternalName() : "");
        convertFunctionMap.put("type", (Function<String, String>) o -> StringUtils.isNotBlank(o) && o.equals("A") ? "A類型" : "B類型");

        ConvertExternalNameWrapper wrapper = BeanUtils.transExternalName(new ConvertExternalNameWrapper(compareResultMap, convertFunctionMap));
        System.out.println(new JSONObject(wrapper.getFromMap()));
        System.out.println(new JSONObject(wrapper.getToMap()));        
//      output
//        {"日期":"2023-08-28 11:29:32","整數":"158","用戶狀態":"啟用","類型":"A類型","十進制":"177.000000"}
//        {"日期":"2023-08-28 11:30:32","整數":"200","用戶狀態":"停用","類型":"B類型","十進制":"307.000000"}
    }
}
```

{% endtab %}

{% tab title="變動前" %}

```json
{
  "日期": "2023-08-28 11:29:32",
  "整數": "158",
  "用戶狀態": "啟用",
  "類型": "A類型",
  "十進制": "177.000000"
}
```

{% endtab %}

{% tab title="變動後" %}

```json
{
  "日期": "2023-08-28 11:30:32",
  "整數": "200",
  "用戶狀態": "停用",
  "類型": "B類型",
  "十進制": "307.000000"
}
```

{% endtab %}
{% endtabs %}

從結果範例可以看到，這種結果通常會應用到後台使用者操作日誌，以及需要紀錄前後結果對照的功能上，以便釐清前後關係。

{% code title="FieldInfo" %}

```java
/**
 * @author caster.hsu
 * @Since 2023/8/11
 */
@Target(value = ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldInfo {
    String desc() default "";

    boolean allowNullValue() default true;

    boolean include() default true;
}
```

{% endcode %}

{% code title="BeanUtils" fullWidth="false" %}

```java
package com.caster.test.bean;

import com.caster.test.bean.anno.FieldInfo;
import com.caster.test.bean.field.FieldChange;
import com.caster.test.bean.field.ConvertExternalNameWrapper;
import com.caster.test.bean.field.FieldWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@Slf4j
public class BeanUtils {
    private static final Map<Class, List<Field>> fieldsMap = new HashMap<Class, List<Field>>();

    /**
     * 檢索類別字段及排除特定注解,並暫存暫存class提高效能。
     *
     * @param clazz
     * @return
     */
    public static List<Field> getAllFields(Class<?> clazz) {
        if (!ClassUtils.isCglibProxyClass(clazz)) {
            List<Field> result = fieldsMap.get(clazz);
            if (result == null) {
                result = _getAllFields(clazz);
                fieldsMap.put(clazz, result);
            }
            return result;
        } else {
            return _getAllFields(clazz);
        }
    }

    private static List<Field> _getAllFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<Field>();
        Collections.addAll(fieldList, clazz.getDeclaredFields());
        excludeField(fieldList);
        Class<?> c = clazz.getSuperclass();
        if (c != null) {
            fieldList.addAll(getAllFields(c));
        }
        return fieldList;
    }

    private static List<Field> excludeField(List<Field> fieldList) {
        return fieldList.stream()
                .filter(o -> o.isAnnotationPresent(FieldInfo.class))
                .filter(o -> o.getAnnotation(FieldInfo.class).include())
                .collect(Collectors.toList());
    }


    /**
     * Get field in class and its super class
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Field getField(Class<?> clazz, String fieldName) {
        for (Field field : clazz.getDeclaredFields()) {
            if (field.getName().equals(fieldName)) {
                return field;
            }
        }
        Class<?> c = clazz.getSuperclass();
        if (c != null) {
            return getField(c, fieldName);
        }
        return null;
    }

    public static Map<String, FieldWrapper> extract(Object obj) {
        Map<String, FieldWrapper> result = new HashMap<String, FieldWrapper>();
        for (Field field : BeanUtils.getAllFields(obj.getClass())) {
            try {
                FieldInfo fieldAnno = field.getAnnotation(FieldInfo.class);
                Object value = PropertyUtils.getProperty(obj, field.getName()); // here has large log out put
                if ((Objects.nonNull(fieldAnno) && fieldAnno.allowNullValue()) || Objects.nonNull(value))
                    result.put(field.getName(), FieldWrapper.create(field, value));
            } catch (Exception e) {
                //log.error("", e);'
                e.printStackTrace();
            }
        }
        return result;
    }

    public static Map<String, FieldChange> compare(Object from, Object to, String... ignoreFields) {
        Map<String, FieldChange> result = new HashMap<String, FieldChange>();
        Map<String, FieldWrapper> valueList1 = extract(from);
        Map<String, FieldWrapper> valueList2 = extract(to);

        Set<String> ignoreFieldSet = new HashSet<String>();
        if (ignoreFields != null)
            Collections.addAll(ignoreFieldSet, ignoreFields);

        for (String name : valueList1.keySet()) {
            if (ignoreFieldSet.contains(name))
                continue;

            FieldWrapper fv1 = valueList1.get(name);
            FieldWrapper fv2 = valueList2.get(name);
            if (fv1 != null && fv2 != null && !fv1.equals(fv2)) {
                result.put(name, new FieldChange(name, fv1, fv2));
            }
        }
        return result;
    }

    public static ConvertExternalNameWrapper transExternalName(ConvertExternalNameWrapper convertWrapper) {
        Map<String, Object> fromMap = new HashMap<>();
        Map<String, Object> toMap = new HashMap<>();
        for (FieldChange f : convertWrapper.getCompareResultMap().values()) {
            fromMap.putAll(f.getFrom().toExternalString((o) -> {
                FieldWrapper wrapper = (FieldWrapper) o;
                String value = wrapper.stringValue();
                if (convertWrapper.getConvertFunctionMap().containsKey(wrapper.getVariableName()))
                    return convertWrapper.getConvertFunctionMap().get(wrapper.getVariableName()).apply(wrapper.getValue());
                return value;
            }));

            toMap.putAll(f.getTo().toExternalString((o) -> {
                FieldWrapper wrapper = (FieldWrapper) o;
                String value = wrapper.stringValue();
                if (convertWrapper.getConvertFunctionMap().containsKey(wrapper.getVariableName()))
                    return convertWrapper.getConvertFunctionMap().get(wrapper.getVariableName()).apply(wrapper.getValue());
                return value;
            }));
        }
        convertWrapper.setFromMap(fromMap);
        convertWrapper.setToMap(toMap);
        return convertWrapper;
    }

}

```

{% endcode %}

以下是針對這五個方法的簡單說明：

1. `getAllFields(Class<?> clazz)`： 這個方法用於獲取一個類及其超類中的所有字段，並根據特定的條件（通過 `FieldInfo` 注解）進行篩選。如果類不是 CGLIB 代理類，則優先從 `fieldsMap` 中獲取，否則調用 `_getAllFields` 方法獲取並存入緩存中。
2. `_getAllFields(Class<?> clazz)`： 這個私有方法實際上執行字段的提取工作。它首先獲取指定類的所有宣告字段，然後通過 `excludeField` 方法進行篩選，接著繼續處理超類的字段，直到所有字段被提取並返回。
3. `excludeField(List<Field> fieldList)`： 這個方法是 `_getAllFields` 中使用的輔助方法，它通過過濾字段列表，只保留標有 `FieldInfo` 注解且允許包含（通過 `include()` 方法）的字段。
4. `extract(Object obj)`： 這個方法從給定的對象中提取字段信息並創建一個映射，其中包含字段名稱和值。它使用 `BeanUtils.getAllFields` 獲取所有字段，然後使用反射獲取字段的值。如果該字段允許空值，或者字段值不為空，則將字段信息添加到映射中。
5. `compare(Object from, Object to, String... ignoreFields)`： 這個方法用於比較兩個對象的字段變更。它使用 `extract` 方法提取兩個對象的字段信息，然後對比這些信息，找到不同的字段。忽略指定的字段（如果有的話），最終返回一個映射，其中包含變更的字段名稱和相關信息。
6. `transExternalName(ConvertExternalNameWrapper convertWrapper)`： 這個方法接受一個 `ConvertExternalNameWrapper` 對象，該對象包含比較結果的映射，將這些比較結果轉換為外部名稱的形式，並返回一個更新後的 `ConvertExternalNameWrapper` 對象。在這個方法中，首先創建了兩個空的映射 `fromMap` 和 `toMap` 用於存儲轉換後的數據。

這五個方法一起提供了一個工具集，用於處理對象的字段操作、篩選和比較。它們基於反射和 Java 的反射 API，讓你可以更便捷地操作和分析對象的字段數據。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xu-min-chang.gitbook.io/caster-develop-note/java/reflect-ying-yong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
