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

Java 反射機制深度實戰:從基礎操作到動態代理 AOP 框架構建

     編輯:Java 相關知識

前言:反射 —— 框架的基石,動態的靈魂

Java 反射 (java.lang.reflect) 允許運行中檢查類結構、實例化物件、調用方法、訪問字段、處理註解。Spring IoC、Hibernate ORM、MyBatis Mapper、JUnit 測試框架、動態代理 AOP 等所有主流框架的核心均建立於反射之上。本文從基礎 API 入手,逐步構建一個完整的動態代理 AOP 攔截器框架,並展示註解掃描與 WebService 發布實戰。

一、 反射核心 API 快速參考

任務關鍵類/方法備註
獲取 Class 物件Class.forName()、.class、obj.getClass()每類型僅一個 Class 實例
掃描構造器getConstructor()、getDeclaredConstructors()前者含繼承/public,後者含私有/本類聲明
掃描字段getField()、getDeclaredFields()同理,setAccessible(true) 破壞封裝
掃描方法getMethod()、getDeclaredMethods()參數型別陣列精確匹配
實例化clazz.newInstance() (Deprecated)、constructor.newInstance()推薦構造器方式,支援帶參
調用方法method.invoke(obj, args...)靜態方法傳 null
讀寫字段field.get(obj)、field.set(obj, val)需 setAccessible(true)
讀取註解getAnnotation()、getAnnotations()、isAnnotationPresent()需 @Retention(RUNTIME)

二、 基礎實戰:類檢查、實例化、方法調用、字段修改

目標類

interface HelloWorldService { void sayHello(String name); }

class MyHelloWorld implements HelloWorldService {
    public String name;  // 故意用 public 字段演示
    public void sayHello(String name) { System.out.println("Hello " + name + "."); }
    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}

1. 掃描類結構

Class clazz = Class.forName("sample.reflection.MyHelloWorld");
Arrays.stream(clazz.getDeclaredMethods()).forEach(m -> System.out.println(m.toGenericString()));
Arrays.stream(clazz.getFields()).forEach(f -> System.out.println(f.toGenericString()));
/* 輸出:
public void sample.reflection.MyHelloWorld.sayHello(java.lang.String)
public java.lang.String sample.reflection.MyHelloWorld.getName()
public void sample.reflection.MyHelloWorld.setName(java.lang.String)
public java.lang.String sample.reflection.MyHelloWorld.name
*/

2. 實例化與方法調用

MyHelloWorld hello = (MyHelloWorld) clazz.getDeclaredConstructor().newInstance();
Method sayHello = clazz.getMethod("sayHello", String.class);
sayHello.invoke(hello, "Zhang San");  // Hello Zhang San.

3. 直接修改字段 (繞過 setter)

Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println("Before: " + nameField.get(hello));  // null
nameField.set(hello, "Zhang San");
System.out.println("After: " + nameField.get(hello));   // Zhang San

三、 註解處理實戰:自定義註解掃描器

定義註解

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

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented
@interface MethodAnnotation { String methodName(); String returnType(); }

@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented
@interface ParameterAnnotation { String value(); }

@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented
@interface FieldAnnotation { String value(); }

使用註解

@ClassAnnotation("類級註解")
class MyClass {
    @MethodAnnotation(methodName="printInfo", returnType="void")
    public void printInfo(String info) { System.out.println(info); }
    
    @MethodAnnotation(methodName="printError", returnType="void")
    public void printError(@ParameterAnnotation("參數註解") String error) { 
        System.err.println(error); 
    }
    
    @FieldAnnotation("字段註解")
    public int count;
}

掃描策略一:全量遍歷 (適合啟動時掃描)

void scanAll(MyClass target) {
    // 類註解
    Arrays.stream(target.getClass().getAnnotations()).forEach(System.out::println);
    // 方法與參數註解
    for (Method m : target.getClass().getDeclaredMethods()) {
        Arrays.stream(m.getAnnotations()).forEach(System.out::println);
        for (Annotation[] pa : m.getParameterAnnotations()) 
            Arrays.stream(pa).forEach(System.out::println);
    }
    // 字段註解
    Arrays.stream(target.getClass().getFields())
        .flatMap(f -> Arrays.stream(f.getAnnotations()))
        .forEach(System.out::println);
}

掃描策略二:精準查找 (適合運行時按需讀取)

void scanPrecise(Class clazz) {
    if (clazz.isAnnotationPresent(ClassAnnotation.class)) {
        System.out.println(clazz.getAnnotation(ClassAnnotation.class));
    }
    for (Method m : clazz.getMethods()) {  // 含繼承
        if (m.isAnnotationPresent(MethodAnnotation.class)) {
            System.out.println(m.getAnnotation(MethodAnnotation.class));
        }
        for (Annotation[] pa : m.getParameterAnnotations()) 
            for (Annotation a : pa) System.out.println(a);
    }
    for (Field f : clazz.getFields()) {
        if (f.isAnnotationPresent(FieldAnnotation.class))
            System.out.println(f.getAnnotation(FieldAnnotation.class));
    }
}

四、 實戰:基於註解的 WebService 發布 (JAX-WS)

@WebService(targetNamespace="https://test", serviceName="HelloService")
public class HelloServiceProvider {
    @WebResult(name="HelloString")
    @WebMethod
    public String sayHello(@WebParam(name="userName") String name) {
        return "Hello " + name;
    }
    
    @Oneway @WebMethod(action="userLogin", operationName="userLogin")
    public void login() { System.out.println("User has logged on."); }
    
    public static void main(String[] args) {
        // JDK 內建輕量級 HTTP 伺服器發布
        Endpoint.publish("https://localhost:8888/test/HelloService", new HelloServiceProvider());
    }
}
// 瀏覽器訪問 https://localhost:8888/test/HelloService?wsdl 獲取 WSDL

五、 核心實戰:手寫動態代理 AOP 框架

1. 定義攔截器介面

interface AOPInterceptor {
    void before(Method method, Object[] args);
    void after(Method method, Object[] args);
    void afterThrowing(Method method, Object[] args);
    void afterFinally(Method method, Object[] args);
}

2. InvocationHandler 實現切面織入

class DynamicProxyInvocationHandler implements InvocationHandler {
    private final Object target;
    private final AOPInterceptor interceptor;
    
    public DynamicProxyInvocationHandler(Object target, AOPInterceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            interceptor.before(method, args);
            Object result = method.invoke(target, args);  // 執行目標方法
            interceptor.after(method, args);
            return result;
        } catch (Throwable t) {
            interceptor.afterThrowing(method, args);
            throw t;  // 保持異常拋出
        } finally {
            interceptor.afterFinally(method, args);
        }
    }
}

3. 代理工廠

interface DynamicProxyFactory {
     T createProxy(Class clazz, T target, AOPInterceptor interceptor);
}

class DynamicProxyFactoryImpl implements DynamicProxyFactory {
    @Override
    public  T createProxy(Class clazz, T target, AOPInterceptor interceptor) {
        InvocationHandler handler = new DynamicProxyInvocationHandler(target, interceptor);
        // 關鍵:Proxy.newProxyInstance(類加載器, 實現介面陣列, handler)
        return (T) Proxy.newProxyInstance(
            Thread.currentThread().getContextClassLoader(), 
            new Class[] { clazz }, 
            handler
        );
    }
}

4. 實現具體攔截邏輯

class MyInterceptor implements AOPInterceptor {
    public void before(Method m, Object[] args) { System.out.println("方法開始執行: " + m.getName()); }
    public void after(Method m, Object[] args) { System.out.println("方法執行結束: " + m.getName()); }
    public void afterThrowing(Method m, Object[] args) { System.out.println("方法拋出異常: " + m.getName()); }
    public void afterFinally(Method m, Object[] args) { System.out.println("方法 finally 執行: " + m.getName()); }
}

5. 測試驗證

public static void main(String[] args) {
    HelloWorldService target = new MyHelloWorld();  // 目標物件
    DynamicProxyFactory factory = new DynamicProxyFactoryImpl();
    HelloWorldService proxy = factory.createProxy(HelloWorldService.class, target, new MyInterceptor());
    
    proxy.sayHello("Zhang San");  // 正常流程
    // 輸出:
    // 方法開始執行: sayHello
    // Hello Zhang San.
    // 方法執行結束: sayHello
    // 方法 finally 執行: sayHello
    
    // 測試異常流程 (在 sayHello 內拋出 RuntimeException)
    // 輸出:
    // 方法開始執行: sayHello
    // 方法拋出異常: sayHello
    // 方法 finally 執行: sayHello
    // Exception in thread "main" java.lang.reflect.UndeclaredThrowableException ...
}

六、 反射效能與最佳實踐

優化點建議
緩存 Class/Method/Field啟動時掃描緩存至 Map,避免重複 getMethod() 解析字節碼
setAccessible(true)一次性設置,JIT 可優化訪問檢查;注意 Java 9+ 模組系統需 --add-opens
優先用 MethodHandle / VarHandleJava 7+ MethodHandles.lookup() 直接生成調用句柄,效能接近直接調用
避免熱點路徑反射高頻調用路徑改用代碼生成 (ASM/ByteBuddy) 或 Lambda 元工廠
模組化封裝Java 9+ 需在 module-info.java 中 opens com.pkg to java.base 允許深度反射

七、 總結

反射賦予了 Java 「運行時自我認知與修改」的動態能力,是框架級開發的基石。掌握 Class、Method、Field、Constructor、Annotation、Proxy 六大核心類,並理解 InvocationHandler 動態代理機制,即可構建 IoC 容器、AOP 框架、ORM 映射、RPC 框架等核心基礎設施。實戰中務必關注緩存優化、模組系統相容性、安全權限,並在極致效能場景考慮 MethodHandle 或字節碼生成替代方案。

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