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

Java 反射與動態代理深度解析:從原理到 AOP 實戰

     編輯:Java 綜合教學

Java 反射與動態代理深度解析:從原理到 AOP 實戰

反射與動態代理是 Java 進階開發的兩大基石,前者賦予程序「自省」能力,後者實現了關注點分離。本文將從類加載機制切入,層層遞進至動態代理實現,最後結合 AOP 展示企業級應用場景。

一、Java 反射核心機制

反射的本質是:在運行時獲取類的完整結構信息(字段、方法、構造器等),並能動態創建實例、調用方法。其核心入口是 java.lang.Class 對象,代表運行時類元數據。

1. 獲取 Class 實例的三種方式

  • Class.forName("全限定類名") - 最常用,支持動態加載
  • 類名.class - 編譯期已知類型時使用
  • 對象.getClass() - 從實例反推類信息

2. 核心操作代碼示例

// 獲取運行時類
Class<StudentExam> clazz = StudentExam.class;

// 實例化(需無參構造器)
StudentExam studentExam = clazz.newInstance();

// 操作字段(含私有)
Field field = clazz.getDeclaredField("id");
field.setAccessible(true); // 關鍵:暴力反射破解私有封裝
field.set(studentExam, 11);

// 調用方法
Method method = clazz.getMethod("setId", Integer.class);
method.invoke(studentExam, 123);

// 獲取所有聲明方法(含私有,不含繼承)
Method[] methods = clazz.getDeclaredMethods();

3. 類加載器 ClassLoader 實戰

類加載器負責將 .class 文件加載進 JVM 內存。利用它可讀取類路徑下的配置文件:

public void classLoader() throws IOException {
    ClassLoader loader = this.getClass().getClassLoader();
    // 路徑從 classpath 根目錄開始,用 / 分隔
    InputStream inStream = loader.getResourceAsStream("config.properties");
    Properties properties = new Properties();
    properties.load(inStream);
    System.out.println(properties.getProperty("domain"));
}

二、動態代理:從靜態到動態的躍遷

1. 靜態代理的痛點

代理類與目標類在編譯期確定,一個代理只能服務一個接口,導致類爆炸,擴展性極差。

2. JDK 動態代理核心三要素

  • 目標對象:實現業務接口的真實對象
  • InvocationHandler:實現代理邏輯的處理器
  • Proxy.newProxyInstance():動態生成代理類實例

3. 經典實現代碼

public class ProxyTest {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        MyInvocationHandler handler = new MyInvocationHandler();
        Subject subject = (Subject) handler.bind(realSubject);
        subject.action(); // 實際調用 handler.invoke()
    }
}

interface Subject { void action(); }

class RealSubject implements Subject {
    @Override public void action() { System.out.println("真實業務邏輯執行..."); }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public Object bind(Object target) {
        this.target = target;
        // 關鍵:類加載器、實現接口、處理器
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【前置增強】方法調用前...");
        Object result = method.invoke(target, args); // 調用真實方法
        System.out.println("【後置增強】方法調用後...");
        return result;
    }
}

三、動態代理與 AOP:企業級應用範式

AOP(面向切面編程)本質上就是動態代理的規範化應用。Spring AOP 底層正是基於 JDK 動態代理(接口場景)或 CGLIB(類場景)實現。

1. 手寫 AOP 代理:日誌切面示例

public class ArithmeticCalculatorLoggingProxy {
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        this.target = target;
    }

    public ArithmeticCalculator getLoggingProxy() {
        return (ArithmeticCalculator) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            new Class[]{ArithmeticCalculator.class},
            (proxy, method, args) -> {
                String methodName = method.getName();
                System.out.println("[Before] " + methodName + " args: " + Arrays.asList(args));
                
                Object result = null;
                try {
                    result = method.invoke(target, args); // 目標方法執行
                    System.out.println("[After Returning] result: " + result);
                } catch (Exception e) {
                    System.out.println("[After Throwing] exception: " + e.getMessage());
                    throw e;
                } finally {
                    System.out.println("[After] 方法結束");
                }
                return result;
            }
        );
    }
}

2. 使用示例

public class Main {
    public static void main(String[] args) {
        ArithmeticCalculator calculator = new ArithmeticCalculatorImpl();
        // 包裝代理
        calculator = new ArithmeticCalculatorLoggingProxy(calculator).getLoggingProxy();
        
        calculator.add(11, 12);  // 自動觸發日誌切面
        calculator.div(21, 3);
    }
}

輸出將包含完整的方法進入、參數、返回值、畂常、退出日誌,且業務代碼零侵入。這正是 Spring @AspectJ 註解驅動的底層邏輯縮影。

四、總結

  • 反射打破了編譯期類型綁定,實現框架級通用邏輯(如 ORM 映射、依賴注入)
  • 動態代理將橫切關注點(日誌、事務、權限)與業務邏輯解耦
  • JDK 動態代理要求目標實現接口;CGLIB 通過繼承代理類,無接口也可用
  • 掌握這兩者,即掌握了 Spring、MyBatis 等主流框架的核心原理
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved