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

Java 動態代理核心陷阱:InvocationHandler 正確實現與 Proxy 內部機制深度解析

     編輯:Java 程式設計入門

問題現場:看似正確卻拋出 UndeclaredThrowableException

// 錯誤版 InvocationHandler
public class TimeInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("startTime: " + System.currentTimeMillis());
        Object obj = method.invoke(proxy, args);  // 關鍵錯誤:傳入 proxy 而非 target
        System.out.println("endTime: " + System.currentTimeMillis());
        return obj;
    }
}

// 測試代碼
TimeInvocationHandler handler = new TimeInvocationHandler();
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserServiceImpl.class.getClassLoader(),
    UserServiceImpl.class.getInterfaces(),
    handler
);
proxy.update(2);  // 觸發無限遞歸 -> StackOverflowError 包裝為 UndeclaredThrowableException

根因分析:Proxy 內部調用鏈

JDK 動態代理生成的 $Proxy0 類繼承 Proxy 並實現目標接口。其核心邏輯:

// Proxy 類簡化結構
protected InvocationHandler h;
protected Proxy(InvocationHandler h) { this.h = h; }

// 生成的代理方法(如 update)
public void update(int id) {
    h.invoke(this, methodOfUpdate, new Object[]{id});  // this = 代理對象實例
}

當 invoke 內部調用 method.invoke(proxy, args) 時,又會觸發代理的 update 方法,形成無限遞歸。

正確實現:持有目標對象引用

// 正確版:構造器注入目標對象
public class TimeInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public TimeInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("startTime: " + System.currentTimeMillis());
        Object result = method.invoke(target, args);  // 關鍵修正:調用目標對象
        System.out.println("endTime: " + System.currentTimeMillis());
        return result;
    }
}

// 測試代碼修正
TimeInvocationHandler handler = new TimeInvocationHandler(new UserServiceImpl());
UserService proxy = (UserService) Proxy.newProxyInstance(
    UserServiceImpl.class.getClassLoader(),
    UserServiceImpl.class.getInterfaces(),
    handler
);

匿名內部類簡化寫法

UserServiceImpl target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("startTime: " + System.currentTimeMillis());
            Object result = method.invoke(target, args);
            System.out.println("endTime: " + System.currentTimeMillis());
            return result;
        }
    }
);

invoke 方法參數深度解讀

參數含義關鍵點
proxy代理實例本身($Proxy0)僅供標識,不可用於方法調用
method接口方法的 Method 對象聲明類為接口,非實現類
args調用參數數組基本類型已自動裝箱

返回值類型與 null 的陷阱

// 接口定義
public interface UserService {
    int save();      // 返回 int
    void update(int id); // 返回 void
}

// 如果 invoke 返回 null
public Object invoke(...) { return null; }

proxy.update(2);  // 正常:void 方法忽略返回值
proxy.save();     // 拋出 NullPointerException:JVM 無法將 null 轉為 int

結論:invoke 必須返回與接口方法聲明兼容的值,基本類型返回值不能為 null。

總結

  1. InvocationHandler 必須持有真實目標對象引用
  2. method.invoke(target, args) 而非 method.invoke(proxy, args)
  3. 代理對象 proxy 僅作標識,不可用於反射調用
  4. 返回值必須匹配接口聲明,基本類型不可為 null
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved