反射允許程式在執行期檢視、修改類的結構(欄位、方法、建構子)、建立實例、調用方法。與編譯期檢查的 RTTI(instanceof、強制轉型)不同,反射處理的是編譯期未知的類型。
每個已載入的類在 JVM 中僅有一個 Class<?> 實例,三種取得方式:
Class<?> c1 = String.class; // 1. 類字面常量(不觸發初始化)
Class<?> c2 = "hello".getClass(); // 2. 實例 getClass()
Class<?> c3 = Class.forName("java.lang.String"); // 3. 完整類名(觸發初始化)
類載入三階段:載入 → 鏈接(驗證、準備、解析) → 初始化。Class.forName 會觸發初始化(執行 static 區塊),而 .class 不會。
| 類別 | 用途 | 常用方法 |
|---|---|---|
Class | 類元數據 | getFields()、getDeclaredFields()、getMethods()、getConstructors()、newInstance() |
Field | 欄位 | get(obj)、set(obj, val)、setAccessible(true) |
Method | 方法 | invoke(obj, args...)、getParameterTypes()、getReturnType() |
Constructor | 建構子 | newInstance(args...)、getParameterTypes() |
public class BeanCopier {
public static void copy(Object src, Object dest) {
Class<?> srcClass = src.getClass();
Class<?> destClass = dest.getClass();
Map<String, Method> setters = Arrays.stream(destClass.getMethods())
.filter(m -> m.getName().startsWith("set") && m.getParameterCount() == 1)
.collect(Collectors.toMap(m -> m.getName().substring(3), m -> m));
for (Method getter : srcClass.getMethods()) {
if (getter.getName().startsWith("get") && getter.getParameterCount() == 0) {
String prop = getter.getName().substring(3);
Method setter = setters.get(prop);
if (setter != null && setter.getParameterTypes()[0].isAssignableFrom(getter.getReturnType())) {
try {
Object val = getter.invoke(src);
setter.invoke(dest, val);
} catch (Exception e) { throw new RuntimeException(e); }
}
}
}
}
}
JDK 動態代理基於介面,在運行時生成實現該介面的代理類,將所有方法調用委派給 InvocationHandler。
Proxy.newProxyInstance(ClassLoader, Class[] interfaces, InvocationHandler)InvocationHandler.invoke(Object proxy, Method method, Object[] args)public interface Service { void doWork(); }
public class RealService implements Service {
public void doWork() { System.out.println("執行業務邏輯..."); }
}
public class TimingHandler implements InvocationHandler {
private final Object target;
public TimingHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
Object result = method.invoke(target, args);
long elapsed = System.nanoTime() - start;
System.out.printf("%s 耗時 %d ms%n", method.getName(), elapsed / 1_000_000);
return result;
}
}
// 使用
Service real = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new TimingHandler(real)
);
proxy.doWork(); // 輸出:doWork 耗時 5 ms
| 特性 | JDK Proxy | CGLIB |
|---|---|---|
| 代理類型 | 介面 | 類(繼承) |
| final 方法 | 不可代理 | 不可代理 |
| 生成方式 | 介面實現 | 字節碼增強(繼承) |
| 性能 | 調用稍慢(反射) | FastClass 機制,調用較快 |
| Spring 默認 | 介面用 JDK,類用 CGLIB | 可強制 proxyTargetClass=true |
Method/Field 或使用 MethodHandle/VarHandle(Java 9+)。setAccessible(true) 可破壞封裝,模組化系統需在 module-info.java 開放 opens。Proxy 專用載入器定義,若目標類在不同模組需注意可見性。