Java Proxy 和 CGLIB 動態代理原理

動態代理在 Java 中有著廣泛的應用,比如 Spring AOP,Hibernate 數據查詢、測試框架的后端 mock、RPC,Java 注解對象獲取等。靜態代理的代理關系在編譯時就確定了,而動態代理的代理關系是在運行時確定的。靜態代理實現簡單,適合于代理類較少且確定的情況,而動態代理則給我們提供了更大的靈活性。今天我們來探討 Java 中兩種常見的動態代理方式:JDK 原生動態代理和 CGLIB 動態代理。

1. JDK 原生動態代理

先從直觀的示例說起,假設我們有一個接口 Hello 和一個簡單實現 HelloImp:

// 接口
interface Hello{
    String sayHello(String str);
}
// 實現
class HelloImp implements Hello{
    @Override
    public String sayHello(String str) {
        return "HelloImp: " + str;
    }
}

這是 Java 中再常見不過的場景,使用接口制定協議,然后用不同的實現來實現具體行為。假設你已經拿到上述類庫,如果我們想通過日志記錄對 sayHello() 的調用,使用靜態代理可以這樣做:

// 靜態代理方式
class StaticProxiedHello implements Hello{
    ...
    private Hello hello = new HelloImp();
    @Override
    public String sayHello(String str) {
        logger.info("You said: " + str);
        return hello.sayHello(str);
    }
}

上例中靜態代理類 StaticProxiedHello 作為 HelloImp 的代理,實現了相同的 Hello 接口。用 Java 動態代理可以這樣做:

  1. 首先實現一個 InvocationHandler,方法調用會被轉發到該類的 invoke() 方法。

  2. 然后在需要使用 Hello 的時候,通過 JDK 動態代理獲取 Hello 的代理對象。

// Java Proxy
// 1. 首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
class LogInvocationHandler implements InvocationHandler{
    ...
    private Hello hello;
    public LogInvocationHandler(Hello hello) {
        this.hello = hello;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("sayHello".equals(method.getName())) {
            logger.info("You said: " + Arrays.toString(args));
        }
        return method.invoke(hello, args);
    }
}
// 2. 然后在需要使用Hello的時候,通過JDK動態代理獲取Hello的代理對象。
Hello hello = (Hello)Proxy.newProxyInstance(
    getClass().getClassLoader(), // 1. 類加載器
    new Class<?>[] {Hello.class}, // 2. 代理需要實現的接口,可以有多個
    new LogInvocationHandler(new HelloImp()));// 3. 方法調用的實際處理者
System.out.println(hello.sayHello("I love you!"));

運行上述代碼輸出結果:

日志信息: You said: [I love you!]
HelloImp: I love you!

上述代碼的關鍵是 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) 方法,該方法會根據指定的參數動態創建代理對象。三個參數的意義如下:

  1. loader,指定代理對象的類加載器;

  2. interfaces,代理對象需要實現的接口,可以同時指定多個接口;

  3. handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這里;

newProxyInstance() 會返回一個實現了指定接口的代理對象,對該對象的所有方法調用都會轉發給 InvocationHandler.invoke() 方法。理解上述代碼需要對 Java 反射機制有一定了解。動態代理神奇的地方就是:

  1. 代理對象是在程序運行時產生的,而不是編譯期。

  2. 對代理對象的所有接口方法調用都會轉發到 InvocationHandler.invoke() 方法,在 invoke() 方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;之后我們通過某種方式執行真正的方法體,示例中通過反射調用了 Hello 對象的相應方法,還可以通過 RPC 調用遠程方法。

注意:對于從 Object 中繼承的方法,JDK Proxy 會把 hashCode()、equals()、toString() 這三個非接口方法轉發給 InvocationHandler,其余的 Object 方法則不會轉發,詳見 JDK Proxy 官方文檔。

如果對 JDK 代理后的對象類型進行深挖,可以看到如下信息:

# Hello代理對象的類型信息
class=class jdkproxy.$Proxy0
superClass=class java.lang.reflect.Proxy
interfaces: 
interface jdkproxy.Hello
invocationHandler=jdkproxy.LogInvocationHandler@a09ee92

代理對象的類型是 jdkproxy.$Proxy0,這是個動態生成的類型,類名是形如 $ProxyN 的形式;父類是 java.lang.reflect.Proxy,所有的 JDK 動態代理都會繼承這個類;同時實現了 Hello 接口,也就是我們接口列表中指定的那些接口。

如果你還對 jdkproxy.$Proxy0 具體實現感興趣,它大致長這個樣子:

// JDK代理類具體實現
public final class $Proxy0 extends Proxy implements Hello
{
  ...
  public $Proxy0(InvocationHandler invocationhandler)
  {
    super(invocationhandler);
  }
  ...
  @Override
  public final String sayHello(String str){
    ...
    return super.h.invoke(this, m3, new Object[] {str});// 將方法調用轉發給invocationhandler
    ...
  }
  ...
}

這些邏輯沒什么復雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。更多詳情,可參考 BrightLoong 的 Java 靜態代理 & 動態代理筆記。

Java 動態代理為我們提供了非常靈活的代理機制,但 Java 動態代理是基于接口的,如果對象沒有實現接口我們該如何代理呢?CGLIB 登場。

2. CGLIB 動態代理

CGLIB (Code Generation Library) 是一個基于 ASM 的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB 通過繼承方式實現代理。

來看示例,假設我們有一個沒有實現任何接口的類 HelloConcrete:

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

因為沒有實現接口該類無法使用 JDK 代理,通過 CGLIB 代理實現如下:

  1. 首先實現一個 MethodInterceptor,方法調用會被轉發到該類的 intercept() 方法。

  2. 然后在需要使用 HelloConcrete 的時候,通過 CGLIB 動態代理獲取代理對象。

// CGLIB動態代理
// 1. 首先實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. 然后在需要使用HelloConcrete的時候,通過CGLIB動態代理獲取代理對象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

運行上述代碼輸出結果:

日志信息: You said: [I love you!]
HelloConcrete: I love you!

上述代碼中,我們通過 CGLIB 的 Enhancer 來指定要代理的目標對象、實際處理代理邏輯的對象,最終通過調用 create() 方法得到代理對象,對這個對象所有非 final 方法的調用都會轉發給 MethodInterceptor.intercept() 方法,在 intercept() 方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;通過調用 MethodProxy.invokeSuper() 方法,我們將調用轉發給原始對象,具體到本例,就是 HelloConcrete 的具體方法。CGLIG 中 MethodInterceptor 的作用跟 JDK 代理中的 InvocationHandler 很類似,都是方法調用的中轉站。

注意:對于從 Object 中繼承的方法,CGLIB 代理也會進行代理,如hashCode()、equals()toString() 等,但是 getClass()、wait() 等方法不會,因為它是 final 方法,CGLIB 無法代理。

如果對 CGLIB 代理之后的對象類型進行深挖,可以看到如下信息:

# HelloConcrete代理對象的類型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces: 
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

我們看到使用 CGLIB 代理之后的對象類型是 cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,這是 CGLIB 動態生成的類型;父類是 HelloConcrete,印證了 CGLIB 是通過繼承實現代理;同時實現了 net.sf.cglib.proxy.Factory 接口,這個接口是 CGLIB 自己加入的,包含一些工具方法。

注意,既然是繼承就不得不考慮 final 的問題。我們知道 final 類型不能有子類,所以 CGLIB 不能代理 final 類型,遇到這種情況會拋出類似如下異常:

java.lang.IllegalArgumentException: Cannot subclass ...

同樣的,final 方法是不能重載的,所以也不能通過 CGLIB 代理,遇到這種情況不會拋異常,而是會跳過 final 方法只代理其他方法。

如果你還對代理類 cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52 具體實現感興趣,它大致長這個樣子:

// CGLIB代理類具體實現
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
  extends HelloConcrete
  implements Factory
{
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...

  public final String sayHello(String paramString)
  {
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      // 將請求轉發給MethodInterceptor.intercept()方法。
      return (String)tmp17_14.intercept(this, 
              CGLIB$sayHello$0$Method, 
              new Object[] { paramString }, 
              CGLIB$sayHello$0$Proxy);
    }
    return super.sayHello(paramString);
  }
  ...
}

上述代碼我們看到,當調用代理對象的 sayHello() 方法時,首先會嘗試轉發給 MethodInterceptor.intercept() 方法,如果沒有 MethodInterceptor 就執行父類的 sayHello()。這些邏輯沒什么復雜之處,但是他們是在運行時動態產生的,無需我們手動編寫。

3. 結語

本文介紹了 Java 兩種常見動態代理機制的用法和原理,JDK 原生動態代理是 Java 原生支持的,不需要任何外部依賴,但是它只能基于接口進行代理;CGLIB 通過繼承的方式進行代理,無論目標對象有沒有實現接口都可以代理,但是無法處理 final 的情況。

動態代理是 Spring AOP(Aspect Orient Programming, 面向切面編程)的實現方式,了解動態代理原理,對理解 Spring AOP 大有幫助。

原文:http://www.cnblogs.com/CarpenterLee/p/8241042.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。