反射是 Java 動態性的核心,框架(Spring、Hibernate、MyBatis)的基石。本文系統闡述 **Class 物件獲取、方法/建構子/欄位反射操作、泛型擦除下的 TypeToken 模式、動態代理、效能優化與安全風險**。
| 方法 | 說明 |
| getName() | 完整限定名(含包名) |
| getSimpleName() | 簡單類名 |
| getPackage() | Package 物件 |
| getModifiers() | 修飾符位掩碼,配合 Modifier 工具類解析 |
| isInterface() / isArray() / isEnum() / isAnnotation() / isPrimitive() | 型別判斷 |
| getSuperclass() | 父類 Class |
| getInterfaces() | 實現的介面陣列 |
| getTypeParameters() | 泛型型別參數 |
二、方法反射
2.1 獲取方法
Class clazz = User.class;
// 所有 public 方法(含繼承)
Method[] publicMethods = clazz.getMethods();
// 所有宣告方法(含 private,不含繼承)
Method[] declaredMethods = clazz.getDeclaredMethods();
// 精確獲取單一方法
Method toString = clazz.getMethod("toString"); // 無參
Method setName = clazz.getDeclaredMethod("setName", String.class); // 有參
2.2 方法資訊解析
Method method = clazz.getDeclaredMethod("save", String.class, int.class);
method.getName(); // "save"
method.getReturnType(); // 回傳型別 Class
method.getParameterTypes(); // 參數型別陣列
method.getParameterCount(); // 參數個數
method.getModifiers(); // public/private/static/final...
method.getAnnotation(Transactional.class); // 註解
method.getGenericReturnType(); // 泛型回傳型別(Type)
method.getGenericParameterTypes(); // 泛型參數型別
2.3 方法調用
User user = new User();
Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.setAccessible(true); // 破壞封裝,存取 private
// 實例方法:第一參數為目標物件
setName.invoke(user, "Alice"); // user.setName("Alice")
// 靜態方法:第一參數為 null 或 Class 物件
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
staticMethod.invoke(null); // 或 staticMethod.invoke(clazz)
2.4 異常處理
- `IllegalAccessException`:權限不足,需 `setAccessible(true)`
- `InvocationTargetException`:目標方法拋出異常,**真實異常在 `getCause()`**
- `IllegalArgumentException`:參數型別/個數不匹配
三、建構子反射
3.1 獲取建構子
Constructor>[] allConstructors = clazz.getConstructors(); // public
Constructor>[] declaredConstructors = clazz.getDeclaredConstructors(); // 全部
// 精確獲取
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
3.2 實例化物件
Constructor ctor = clazz.getDeclaredConstructor(String.class);
ctor.setAccessible(true);
User user = ctor.newInstance("Bob"); // 等同 new User("Bob")
// 無參建構子快捷方式(已棄用 Java 9+,不推薦)
// User user = clazz.newInstance();
四、欄位反射
4.1 獲取欄位
Field[] publicFields = clazz.getFields(); // public 含繼承
Field[] allFields = clazz.getDeclaredFields(); // 全部含 private
Field nameField = clazz.getDeclaredField("name");
4.2 讀寫欄位值
User user = new User();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
// 讀取
String name = (String) nameField.get(user);
// 寫入
nameField.set(user, "Charlie");
// 靜態欄位:目標物件傳 null
Field staticField = clazz.getDeclaredField("COUNT");
staticField.setAccessible(true);
int count = (int) staticField.get(null);
staticField.set(null, 100);
4.3 泛型欄位型別獲取
class Container { List items; }
Field itemsField = Container.class.getDeclaredField("items");
Type genericType = itemsField.getGenericType(); // ParameterizedType
if (genericType instanceof ParameterizedType pt) {
Type[] actualTypes = pt.getActualTypeArguments(); // [T]
// 需具體化類型時才能解析 T,否則為 TypeVariable
}
五、泛型擦除下的 TypeToken 模式
// 核心類(Guava/Gson 模式)
abstract class TypeToken {
final Type type;
protected TypeToken() {
Type superclass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
public Type getType() { return type; }
}
// 使用:匿名子類捕獲泛型資訊
TypeToken> token = new TypeToken>() {};
Type type = token.getType(); // ParameterizedType: List
六、動態代理
6.1 JDK 動態代理
interface Service { void execute(); }
class RealService implements Service { public void execute() { System.out.println("Real"); } }
Service real = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
(proxyObj, method, args) -> {
System.out.println("Before: " + method.getName());
Object result = method.invoke(real, args);
System.out.println("After: " + method.getName());
return result;
}
);
proxy.execute(); // 觸發攔截
6.2 CGLIB 代理(無介面)
// 依賴 cglib 套件
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Before CGLIB");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After CGLIB");
return result;
});
RealService proxy = (RealService) enhancer.create();
proxy.execute();
七、效能優化與最佳實踐
| 場景 | 優化策略 |
| 頻繁反射調用 | 緩存 Method/Constructor/Field 物件;`setAccessible(true)` 一次性設定 |
| 建構子實例化 | 優先 `Constructor.newInstance()`;避免 `Class.newInstance()`(已棄用、不處理異常) |
| 方法調用 | 考慮 `MethodHandle` (Java 7+) 或 `VarHandle` (Java 9+),JIT 優化更好 |
| 啟動期掃描 | 編譯期註解處理器 (APT) 生成索引,運行期讀取索引避免掃描 |
| 私有存取 | Java 9+ 模組系統需 `module-info.java` 開放 `opens com.pkg to java.base` |
八、安全風險與模組化限制
- 破壞封裝:`setAccessible(true)` 可存取私有成員,違反封裝原則
- 模組系統限制:Java 9+ 強封裝,非開放包無法反射私有成員,需 `--add-opens` 或 `module-info.java` `opens`
- 安全管理器:`SecurityManager` 可限制 `suppressAccessChecks` 權限
- 序列化攻擊**:反射可構造惡意物件圖,配合 `ObjectInputFilter` 防禦
總結
反射賦予 Java 動態載入、檢查、修改類行為的能力,是框架級開發的利器。**核心掌握點**:Class 物件三種獲取、Method/Constructor/Field 三大反射操作、泛型 TypeToken 捕獲、動態代理兩大實現、效能緩存策略、模組化封裝限制。業務代碼慎用反射,框架代碼善用反射,始終遵循「編譯期可解決不拖到運行期」原則。
- 上一篇文章: Java UDP 網路程式設計完整指南:協議原理、DatagramSocket 實戰與高併發架構模式
- 下一篇文章: Java 陣列刪除元素高效算法:末尾覆蓋法、Arrays.copyOf 與 System.arraycopy 深度對比
Copyright ©
程式碼之家 All Rights Reserved