反射是 Java 動態性的基石,框架(Spring、Hibernate、MyBatis)的核心實現均建立於此。本文系統梳理反射 API 核心用法,重點剖析 Method.invoke 參數語義與常見誤區,附帶可直接復用的工具方法。
| 類/介面 | 核心作用 |
|---|---|
| Class<T> | 類的運行期元數據入口 |
| Field | 成員變量操作(讀/寫、修飾符、泛型類型) |
| Method | 方法操作(調用、參數/返回類型、異常、註解) |
| Constructor<T> | 構造器操作(新建實例、參數類型、註解) |
| Array | 數組動態創建與訪問 |
public Object getProperty(Object owner, String fieldName) throws Exception {
Class<?> ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName); // 僅 public 字段
// Field field = ownerClass.getDeclaredField(fieldName); // 含私有,需 setAccessible(true)
return field.get(owner);
}
關鍵點:getField 只能獲取 public 字段(含繼承);getDeclaredField 獲取本類聲明的所有字段(含私有),需呼叫 field.setAccessible(true) 破壞封裝。
public Object getStaticProperty(String className, String fieldName) throws Exception {
Class<?> ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
return field.get(null); // 靜態字段傳 null 或 Class 對象均可
}
public Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {
Class<?> ownerClass = owner.getClass();
Class<?>[] argsClass = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argsClass[i] = args[i].getClass(); // 注意:基本類型參數會得到包裝類,可能導致匹配失敗
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
Method.invoke 參數詳解:
owner:方法接收者(實例對象),靜態方法傳 null。args:可變參數陣列,對應方法形參列表;基本類型需傳包裝類實例,陣列參數需傳陣列對象(非展開)。null。IllegalAccessException(無權限)、InvocationTargetException(目標方法拋出異常,真實異常在 getCause() 中)。public Object invokeStaticMethod(String className, String methodName, Object[] args) throws Exception {
Class<?> ownerClass = Class.forName(className);
Class<?>[] argsClass = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args); // 第一參數傳 null
}
public Object newInstance(String className, Object[] args) throws Exception {
Class<?> clazz = Class.forName(className);
Class<?>[] argsClass = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argsClass[i] = args[i].getClass();
}
Constructor<?> cons = clazz.getConstructor(argsClass);
return cons.newInstance(args);
}
// 無參構造器簡化版
public Object newInstanceSimple(String className) throws Exception {
return Class.forName(className).getDeclaredConstructor().newInstance();
}
// instanceof 替代品
public boolean isInstance(Object obj, Class<?> cls) {
return cls.isInstance(obj);
}
// 動態獲取數組元素
public Object getArrayElement(Object array, int index) {
return Array.get(array, index);
}
// 錯誤示範:基本類型參數
Method m = clazz.getMethod("setAge", int.class); // 正確:用 int.class
// Method m = clazz.getMethod("setAge", Integer.class); // 錯誤:找不到方法
// 調用時
m.invoke(obj, 25); // 自動裝箱為 Integer,內部會拆箱匹配 int
規則:getMethod 參數類型必須與方法聲明完全一致(基本類型用 int.class、陣列用 String[].class);invoke 實參允許裝箱/拆箱與多態賦值。
Method m = clazz.getDeclaredMethod("internalCalc", int.class);
m.setAccessible(true); // 關鍵:破壞封裝
Object result = m.invoke(target, 100);
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause(); // 獲取真實業務異常
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
if (cause instanceof Exception) throw (Exception) cause;
throw new RuntimeException(cause);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Reflection access denied", e);
}
MethodHandle(Java 7+)。MethodHandles.Lookup 生成的調用點接近直接調用性能。public static void copyProperties(Object source, Object target) {
Class<?> srcClass = source.getClass();
Class<?> tgtClass = target.getClass();
for (Field srcField : srcClass.getDeclaredFields()) {
srcField.setAccessible(true);
try {
Field tgtField = tgtClass.getDeclaredField(srcField.getName());
tgtField.setAccessible(true);
if (tgtField.getType().isAssignableFrom(srcField.getType())) {
tgtField.set(target, srcField.get(source));
}
} catch (NoSuchFieldException ignored) {
// 目標無對應字段,忽略
}
}
}
反射賦予了 Java 動態組裝、掃描、代理的能力,但「大權在手,責任在肩」。生產代碼中請遵循:最小權限原則(僅開放必要訪問)、緩存元數據對象、妥善處理檢查型異常、關鍵路徑考慮 MethodHandle 替代。