Java 動態代理


動態代理是 Java 反射的一種使用場景,只要有一個接口,就能在運行時動態生成類型安全的字節碼文件,可實現資源延遲加載(同傳統代理模式的能力)、切面增強等功能,可以說是 Spring 的基石之一。

這篇文章主要介紹 Java 動態代理的使用,JDK 提供的與動態代理有關的類庫等。

先看下方類圖(細節很多,后續講到相關內容會提示返回來再看):

  • Proxy 和 InvocationHandler 是 JDK 提供的類庫
  • UserService 和 UserServiceImpl 是被代理的演示代碼
  • BusinessInvocationHandler 是實現代理邏輯的演示代碼
  • 最終會生成動態代理類和動態代理實例。

UserService 代碼:

public interface UserService {

    /**
     * 一個簡單的業務函數,用於演示動態代理
     *
     * @param username username
     * @param password password
     */
    void login(String username, String password);

}

UserServiceImpl 代碼:

public class UserServiceImpl implements UserService {

    @Override
    public void login(String username, String password) {
        log.info("user {} login", username);
    }

}

本文的詳細代碼可在筆者 Github 的 JavaTutorials 倉庫找到。

動態代理類

獲取 Class 對象

Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException

該函數用於創建動態代理類的 Class 對象,動態代理類 Class 對象會使用 loader 指定的類加載器加載、將實現 interfaces 指定的全部接口。

參數 loader 和 interfaces 必須滿足以下要求:

  • interfaces 中的對象必須都是接口,不能是普通類或基本數據類型。
  • interfaces 中不能有重復的接口。
  • interfaces 中的所有接口對類加載器 loader 而言都是按名稱可見的,用代碼體現就是(interface 是參數 interfaces 中某個 Class 對象、loader 就是對應加載器): Class.forName(interface.getName(), false, loader) == interface
  • interfaces 中所有的非 public 接口都必須在同一個包路徑下,否則無法生成代理類。
  • 如果 interfaces 中不同接口內定義了相同簽名(函數名和形參列表一致)的方法:
    • 只要有一個方法的返回值類型是 void 或基本數據類型,所有的方法都必須具備相同的返回類型。
    • 其中一個方法的返回值類型必須是其他所有方法返回值類型的子類(或實現類)
  • 動態代理類也受虛擬機限制,因此 interfaces 參數長度不能超過 65535。

如果任意條件不滿足,會拋出 IllegalArgumentException 異常。

動態代理類特點

所有的動態代理類都含有以下特點:

  • 動態代理類繼承 java.lang.reflect.Proxy ,自身都是 public、final、非 abstract 的。

  • 動態代理類名都以 “$Proxy" 開頭,如果 interfaces 中有一個非 public 的接口,生成的動態代理類在該接口的包路徑下。

  • 動態代理類以固定的順序(interfaces 的順序)實現所有接口,如果使用不同的順序再次調用會生成另一個 Class 對象,使用相同的順序再次調用會直接返回已有的 Class 對象,用代碼體現就是:

    //interfaces 參數順序的影響
    Class<?> listSetProxy = Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), List.class, Set.class);
    Class<?> setListProxy = Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), Set.class, List.class);
    log.info("listSetProxy hashCode: {}", listSetProxy.hashCode());
    log.info("setListProxy hashCode: {}", setListProxy.hashCode());
    //兩個測試都通過
    assertNotEquals(listSetProxy, setListProxy);
    assertEquals(listSetProxy, Proxy.getProxyClass(ClassLoader.getPlatformClassLoader(), List.class, Set.class));
    
  • Proxy#isProxyClass 可用於檢查指定的 Class 對象是否為動態代理。

  • 動態代理類的實例持由專門的調用處理器接口(InvocationHandler,下方詳細介紹)實現組合而成,同時繼承 Proxy 類,文章開頭的類圖也體現了這種關系。

創建實例

上文已經提到動態代理類實例由調用處理器組合而成(Proxy 類的 protected 屬性),創建實例時必須指定一個調用處理器接口的特定實現。有以下兩種方式可以獲取一個動態代理實例:

  • 獲取到對應的 Class 對象后,通過反射的手段創建實例(升級到 Java 9 之后,Jigsaw 項目給動態代理創建實例的過程帶來一點影響,Proxy#getProxyClass 已經標記為廢棄。):

    //實際使用中,多半是由 Spring 創建
    UserService realImpl = new UserServiceImpl();
    
    Class<?> proxyClass = Proxy.getProxyClass(realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces());
    Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
    UserService instance = (UserService) constructor.newInstance(new BusinessInvocationHandler(realImpl));
    
  • 如果沒有提前獲取到 Class 對象,可使用 Proxy#newProxyInstance 函數直接創建實例:

    //實際使用中,多半是由 Spring 創建
    UserService realImpl = new UserServiceImpl();
    
    UserService instance = (UserService) Proxy.newProxyInstance(
      realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces(),
      new BusinessInvocationHandler(realImpl)
    );
    

調用處理器

InvocationHandler 接口中定義了一個函數:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

在動態代理實例上調用每個接口定義的函數時,實際都會調用 invoke 函數,將相關信息通過參數傳遞,具體表現如下:

  • 參數 proxy 是動態代理實例自身。
  • 參數 method 是定義在接口中、期望調用的函數,在 invoke 函數實現中可以自由選擇是否真實調用(回顧一下 ER 圖,動態代理類和 InvocationHandler 實現是組合關系,而 UserServiceImpl 是聚合關系)。
  • 參數 args 是傳遞給 method 的實參,在 invoke 函數實現中可任意使用。
  • invoke 函數的返回值會返回給實際調用的函數。
  • invoke 函數內拋出的異常也會在實際調用的函數處拋出。
  • 在動態代理實例上調用 toString, hashCodeequals 同樣也會傳遞給 invoke 函數,一般情況下會直接調用被代理實例的相關函數。

假設 BusinessInvocationHandler 實現如下:

public class BusinessInvocationHandler implements InvocationHandler {

    /**
     * 被代理的對象,實際使用中多半是由 Spring 容器注入
     */
    private final UserService service;

    public BusinessInvocationHandler(UserService service) {
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        log.info("invoke {}'s {} with args {} start", service.getClass().getSimpleName(), method.getName(), Arrays.toString(args));
        Object result = method.invoke(service, args);
        log.info("invoke {}'s {} with args {} done", service.getClass().getSimpleName(), method.getName(), Arrays.toString(args));
        return result;
    }

}

實際使用時代碼如下:

//先獲取動態代理實例
UserService proxyService = (UserService) Proxy.newProxyInstance(
  realImpl.getClass().getClassLoader(), realImpl.getClass().getInterfaces(),
  new BusinessInvocationHandler(realImpl)
);
//調用接口函數,觀察日志可以發現實際調用了 BusinessInvocationHandler#invoke 函數
proxyService.login("cncsl", "password");
//toString, hashCode 和 equals 也會轉發給 invoke,最終 invoke 轉給 realImpl 實例
log.info("dynamicProxyInstance toString: {}, realImpl toString: {}", proxyService, realImpl);
log.info("dynamicProxyInstance hashCode: {}, realImpl hashCode: {}", proxyService.hashCode(), realImpl.hashCode());
assertTrue(proxyService.equals(realImpl));

生成的動態代理類

在 JVM 啟動參數中添加以下內容,JVM 會將生成的代理類字節碼文件輸出到類路徑中:

  • Java 8 及更早版本:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
  • Java 8 以上版本:jdk.proxy.ProxyGenerator.saveGeneratedFiles=true

反編譯字節碼文件后得到下方源碼,筆者添加了一些注釋方便理解。

//Java 8以前,動態代理類一般都位於 com.sum.proxy 包路徑中(或者實現的非 public 接口的包路徑中)
package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import pers.cncsl.jt.reflect.dynamicproxy.UserService;
//繼承 Proxy、實現 UserService
public final class $Proxy0 extends Proxy implements UserService {
		// m0~m2 分別是 hashCode、equals 和 toString 方法
    private static Method m0;
    private static Method m1;
    private static Method m2;
    // 被代理接口中定義的方法,本例具體是 UserService 的 login 方法
    private static Method m3;
		//類加載時就初始化
    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("pers.cncsl.jt.reflect.dynamicproxy.UserService").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
		//父類構造函數會將 InvocationHandler 實例賦值給 h 屬性
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    //動態代理類的所有函數都通過掉用處理器工作
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void login(String var1, String var2) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM