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

Java 反射機制完整指南:從基礎操作到動態代理與工廠模式實戰

     編輯:Java 相關知識

前言

Java 反射是框架開發的基石,賦予程式在運行時檢查、修改類結構與行為的能力。本文以案例驅動方式,系統講解 Class 獲取、實例化、構造器/方法/屬性操作、泛型擦除應對、動態代理、類加載器、以及反射賦能工廠模式的完整實戰。

一、獲取 Class 對象的三種方式

// 1. Class.forName("全限定名") - 最常用,支援動態載入
Class clazz1 = Class.forName("com.example.User");

// 2. 實例.getClass() - 已有實例時
User user = new User();
Class clazz2 = user.getClass();

// 3. 類字面常量 - 編譯期可知,效能最好
Class clazz3 = User.class;

// 4. 基本類型與陣列
Class intClass = int.class;
Class arrClass = int[].class;

二、實例化對象

1. 無參構造器(推薦)

// 方式 A:Class.newInstance() - 已棄用 (Java 9+)
User u1 = (User) clazz.newInstance(); // 需公開無參構造,拋出所有異常

// 方式 B:Constructor.newInstance() - 標準做法
Constructor ctor = clazz.getDeclaredConstructor();
ctor.setAccessible(true); // 支援 private
User u2 = ctor.newInstance();

2. 帶參構造器

// 獲取所有公開構造器
Constructor[] ctors = clazz.getConstructors();

// 獲取指定參數構造器
Constructor ctor = clazz.getDeclaredConstructor(String.class, int.class);
ctor.setAccessible(true);
User u3 = ctor.newInstance("Alice", 25);

三、操作屬性

// 獲取所有宣告屬性(含 private,不含繼承)
Field[] fields = clazz.getDeclaredFields();

for (Field f : fields) {
    int mod = f.getModifiers();
    System.out.println(Modifier.toString(mod) + " " + f.getType().getName() + " " + f.getName());
    
    // 讀寫私有屬性
    f.setAccessible(true);
    Object val = f.get(obj);      // 讀取
    f.set(obj, newValue);         // 寫入
}

靜態屬性操作

// 靜態屬性 obj 參數傳 null
Field staticField = clazz.getDeclaredField("COUNT");
staticField.setAccessible(true);
staticField.set(null, 100); // 設定靜態值

四、調用方法

// 獲取所有公開方法(含繼承)
Method[] methods = clazz.getMethods();

// 獲取指定方法
Method sayHello = clazz.getDeclaredMethod("sayHello", String.class, int.class);
sayHello.setAccessible(true);

// 調用實例方法
Object result = sayHello.invoke(obj, "World", 2024);

// 調用靜態方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
staticMethod.invoke(null); // obj 為 null

處理泛型擦除:獲取實際類型參數

// 假設 class UserDao extends BaseDao
Type superclass = clazz.getGenericSuperclass();
if (superclass instanceof ParameterizedType pt) {
    Type[] actualTypes = pt.getActualTypeArguments();
    // actualTypes[0] -> User.class, actualTypes[1] -> Long.class
}

五、獲取類結構信息

// 父類
Class superClazz = clazz.getSuperclass();

// 實現的介面
Class[] interfaces = clazz.getInterfaces();

// 註解
Annotation[] annotations = clazz.getAnnotations();
MyAnnotation ann = clazz.getAnnotation(MyAnnotation.class);

// 修飾符
int mod = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(mod);
boolean isAbstract = Modifier.isAbstract(mod);

六、陣列操作

int[] arr = {1,2,3,4,5};
Class compType = arr.getClass().getComponentType(); // int.class
int len = Array.getLength(arr); // 5
Object first = Array.get(arr, 0); // 1
Array.set(arr, 0, 100); // 修改第一個元素

// 動態擴容陣列
int[] newArr = (int[]) Array.newInstance(compType, 15);
System.arraycopy(arr, 0, newArr, 0, arr.length);

七、動態代理

JDK 動態代理(基於介面)

interface Subject { String say(String name, int age); }

class RealSubject implements Subject {
    public String say(String name, int age) { return name + " " + age; }
}

class MyInvocationHandler implements InvocationHandler {
    private final Object target;
    public MyInvocationHandler(Object target) { this.target = target; }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After: " + method.getName());
        return result;
    }
}

// 使用
Subject real = new RealSubject();
Subject proxy = (Subject) Proxy.newProxyInstance(
    real.getClass().getClassLoader(),
    real.getClass().getInterfaces(),
    new MyInvocationHandler(real)
);
proxy.say("Bob", 30);

CGLIB 代理(繼承類,Spring 預設)


// 需引入 cglib 依賴
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("CGLIB Before");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("CGLIB After");
    return result;
});
Subject cglibProxy = (Subject) enhancer.create();

八、類加載器體系

// 獲取類加載器
ClassLoader cl = clazz.getClassLoader(); // AppClassLoader
ClassLoader parent = cl.getParent();     // PlatformClassLoader (Ext)
ClassLoader bootstrap = parent.getParent(); // null (Bootstrap, C++實現)

// 線程上下文類加載器(SPI、JNDI、JDBC 常用)
Thread.currentThread().getContextClassLoader();

雙親委派模型

  1. 子類加載器收到請求,先委派給父加載器。
  2. 父加載器無法加載(找不到類)時,子加載器嘗試加載。
  3. 打破雙親委派:自定義 ClassLoader 覆寫 loadClass(如 Tomcat、OSGi、熱部署)。

九、反射賦能工廠模式演進

階段 1:傳統硬編碼工廠(違反開閉原則)

class FruitFactory {
    public static Fruit getFruit(String name) {
        if ("Apple".equals(name)) return new Apple();
        if ("Orange".equals(name)) return new Orange();
        return null; // 新增水果必須修改工廠類
    }
}

階段 2:反射工廠(開閉原則)

class ReflectFruitFactory {
    public static Fruit getFruit(String className) {
        try {
            return (Fruit) Class.forName(className).getDeclaredConstructor().newInstance();
        } catch (Exception e) { throw new RuntimeException(e); }
    }
}
// 使用:ReflectFruitFactory.getFruit("com.example.Apple")

階段 3:配置驅動工廠(解耦類名)

// fruits.properties
apple=com.example.Apple
orange=com.example.Orange

class ConfigFruitFactory {
    private static final Properties PROPS = loadProps();
    
    public static Fruit getFruit(String key) {
        String className = PROPS.getProperty(key);
        if (className == null) throw new IllegalArgumentException("Unknown fruit: " + key);
        return ReflectFruitFactory.getFruit(className);
    }
}

階段 4:註解掃描自動註冊(Spring 風格)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FruitType { String value(); }

@FruitType("apple")
class Apple implements Fruit {}

@ComponentScan("com.example")
class AutoFruitFactory {
    private final Map registry = new ConcurrentHashMap<>();
    
    public AutoFruitFactory() {
        // 掃描 @FruitType 註解類,實例化註冊
    }
    public Fruit getFruit(String key) { return registry.get(key); }
}

十、反射效能優化與安全

效能優化

  • 緩存 Class/Method/Field/Constructor:反射操作昂貴,靜態緩存復用。
  • setAccessible(true):關閉存取檢查,提速 2-3 倍(Java 9+ 模組化可能受限)。
  • MethodHandle / VarHandle (Java 7+):更底層、更快、支援簽名多態。
  • 避免熱路徑反射:如序列化、ORM 映射,啟動時預編譯/生成字節碼。

安全限制

  • SecurityManager 檢查 ReflectPermission("suppressAccessChecks")。
  • Java 9+ 模組系統:opens 關鍵字控制反射訪問權限。
  • --add-opens 啟動參數強制開放。

十一、實戰檢查清單

操作推薦 API注意事項
獲取 ClassType.class / Class.forName()forName 可能拋 ClassNotFoundException
實例化Constructor.newInstance()必須 setAccessible(true) 支援私有構造
讀寫屬性Field.get/set()static 屬性 obj 傳 null
調用方法Method.invoke()異常包裝在 InvocationTargetException 中
泛型類型ParameterizedType僅在聲明處保留,運行時擦除
動態代理Proxy.newProxyInstance()只能代理介面,類代理用 CGLIB/ByteBuddy

總結

Java 反射是「運行時元編程」的核心工具:

  • 基礎能力:Class 載入、實例化、屬性/方法/構造器操作、泛型擦除應對、陣列操作。
  • 進階應用:動態代理(AOP 基石)、類加載器定制(熱部署、隔離)、框架級工廠/IOC 實現。
  • 代價:效能開銷、安全限制、代碼可讀性下降、編譯期類型安全喪失。
  • 原則:框架層使用,業務層避免;熱路徑緩存/替換;模組化時代注意 opens 聲明。

掌握反射,即掌握了構建靈活、可擴展框架的鑰匙。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved