首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深度解析 Java 反射機制:原理、核心 API 與動態代理實戰

     編輯:Java 相關知識

反射是什麼?

反射允許程式在執行期檢視、修改類的結構(欄位、方法、建構子)、建立實例、調用方法。與編譯期檢查的 RTTI(instanceof、強制轉型)不同,反射處理的是編譯期未知的類型。

Class 物件:反射的入口

每個已載入的類在 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 不會。

核心反射 API

類別用途常用方法
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

動態代理 vs CGLIB

特性JDK ProxyCGLIB
代理類型介面類(繼承)
final 方法不可代理不可代理
生成方式介面實現字節碼增強(繼承)
性能調用稍慢(反射)FastClass 機制,調用較快
Spring 默認介面用 JDK,類用 CGLIB可強制 proxyTargetClass=true

效能與安全考量

  • 效能:反射調用比直接調用慢 10-100 倍(視 JVM 優化),熱點路徑建議緩存 Method/Field 或使用 MethodHandle/VarHandle(Java 9+)。
  • 安全:setAccessible(true) 可破壞封裝,模組化系統需在 module-info.java 開放 opens。
  • 類載入器:動態代理類由 Proxy 專用載入器定義,若目標類在不同模組需注意可見性。

現代替代方案

  • MethodHandle / VarHandle(java.lang.invoke):更底層、更快、支援方法引用語法。
  • ByteBuddy / ASM:字節碼生成庫,構建高效代理、AOP 框架基石。
  • Record / Pattern Matching(Java 16+):資料導向編程減少樣板碼,降低反射需求。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved