天天看點

Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

動态代理在Java中有着廣泛的應用,比如Spring AOP,Hibernate資料查詢、測試架構的後端mock、RPC,Java注解對象擷取等。靜态代理的代理關系在編譯時就确定了,而動态代理的代理關系是在編譯期确定的。靜态代理實作簡單,适合于代理類較少且确定的情況,而動态代理則給我們提供了更大的靈活性。

今天我們來探讨Java中兩種常見的動态代理方式:JDK原生動态代理和CGLIB動态代理。

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動态代理可以這樣做:

  • 首先實作一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。
  • 然後在需要使用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!"));           

運作上述代碼輸出結果:

Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

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

  • loader,指定代理對象的類加載器;
  • interfaces,代理對象需要實作的接口,可以同時指定多個接口;
  • handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這裡(*注意1)。

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

  • 代理對象是在程式運作時産生的,而不是編譯期;
  • 對代理對象的所有接口方法調用都會轉發到InvocationHandler.invoke()方法,在invoke()方法裡我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;之後我們通過某種方式執行真正的方法體,示例中通過反射調用了Hello對象的相應方法,還可以通過RPC調用遠端方法。
Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

如果對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接口,也就是我們接口清單中指定的那些接口。擴充:Java面試題内容聚合

如果你還對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
    ...
  }
  ...
}           

這些邏輯沒什麼複雜之處,但是他們是在運作時動态産生的,無需我們手動編寫。更多詳情,可參考:

Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

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

CGLIB動态代理

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

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

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

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

  • 首先實作一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法。
  • 然後在需要使用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!"));           
Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

上述代碼中,我們通過CGLIB的Enhancer來指定要代理的目标對象、實際處理代理邏輯的對象,最終通過調用create()方法得到代理對象,對這個對象所有非final方法的調用都會轉發給MethodInterceptor.intercept()方法,在intercept()方法裡我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;

通過調用MethodProxy.invokeSuper()方法,我們将調用轉發給原始對象,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調用的中轉站。

Java中的原生動态代理和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 class cglib.HelloConcrete

同樣的,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()。這些邏輯沒什麼複雜之處,但是他們是在運作時動态産生的,無需我們手動編寫。

更多關于CGLIB的介紹可以參考:

Java中的原生動态代理和CGLIB動态代理的原理,我不信你全知道!

結語

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

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

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-12

本文作者:CarpenterLee

本文來自:“

網際網路架構師 微信公衆号

”,了解相關資訊可以關注“

網際網路架構師