国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java動態(tài)代理 jdk和cglib的實現(xiàn)比較

h9911 / 857人閱讀

摘要:與靜態(tài)代理對比,動態(tài)代理是在動態(tài)生成代理類,由代理類完成對具體方法的封裝,實現(xiàn)的功能。本文將分析中兩種動態(tài)代理的實現(xiàn)方式,和,比較它們的異同。那如何動態(tài)編譯呢你可以使用,這是一個封裝了的庫,幫助你方便地實現(xiàn)動態(tài)編譯源代碼。

發(fā)現(xiàn)Java面試很喜歡問Spring AOP怎么實現(xiàn)的之類的問題,所以寫一篇文章來整理一下。關于AOP和代理模式的概念這里并不做贅述,而是直奔主題,即AOP的實現(xiàn)方式:動態(tài)代理。與靜態(tài)代理對比,動態(tài)代理是在runtime動態(tài)生成Java代理類,由代理類完成對具體方法的封裝,實現(xiàn)AOP的功能。

本文將分析Java中兩種動態(tài)代理的實現(xiàn)方式,jdk proxycglib,比較它們的異同。本文并不會過多地分析jdk和cglib的源碼去探究底層的實現(xiàn)細節(jié),而只關注最后生成的代理類應該是什么樣的,如何實現(xiàn)代理。只是我個人的整理和思考,和真正的jdk,cglib的產(chǎn)生的結果可能不盡相同,但從原理上來講是一致的。

文章的最后也會探討如何自己實現(xiàn)一個簡單的動態(tài)代理,并提供我自己實現(xiàn)的簡單版本,當然僅供參考。

JDK Proxy

這是Java反射包java.lang.reflect提供的動態(tài)代理的方式,這種代理方式是完全基于接口的。這里先給出一個簡單的例子。

定義接口:

interface ifc {
  int add(int, int);
}

然后是接口ifc的實現(xiàn)類Real

class Real implements ifc {
  @Override
  public int add(int x, int y) {
    return x + y;
  }

Real就是我們需要代理的類,比如我們希望在調(diào)用add的前后打印一些log,這實際上就是AOP了。我們需要最終產(chǎn)生一個代理類,實現(xiàn)同樣的接口ifc,執(zhí)行Real.add的功能,但需要增加一行新的打印語句。這一切對用戶是透明的,用戶只需要關心接口的調(diào)用。為了能在Real.add的周圍添加額外代碼,動態(tài)代理都是通過一種類似方法攔截器的東西來實現(xiàn)的,在Java Proxy里這就是InvocationHandler.

class Handler implements InvocationHandler {
  private final Real real;

  public Handler(Real real) {
    this.real = real;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {
    System.out.println("=== BEFORE ===");
    Object re = method.invoke(real, args);
    System.out.println("=== AFTER ===");
    return re;
  }
}

這里最關鍵的就是invoke方法,實際上代理類的add方法,以及其它方法(如果接口還定義了其它方法),最終都只是調(diào)用這個Handlerinvoke方法,由你來具體定義在invoke里需要做什么,通常就是調(diào)用真正實體類Real的方法,這里就是add,以及額外的AOP行為(打印 BEFORE 和 AFTER)。所以可想而知,代理類里必然是有一個InvocationHandler的實例的,所有的接口方法調(diào)用都會由這個handler實例來代理。

所以我們應該能大概刻畫出這個代理類的模樣:

public ProxyClass implements ifc {
  private static Method mAdd;

  private InvocationHandler handler;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
  }
}

這個版本非常簡單,但已足夠實現(xiàn)我們的要求。我們來觀察這個類,首先毋庸置疑它實現(xiàn)了ifc接口,這是代理模式的根本。它的add方法直接調(diào)用InvocationHandler實例的invoke方法,傳入三個參數(shù),第一個是代理類本身this指針,第二個是add方法的反射類,第三個是參數(shù)列表。所以在invoke方法里,用戶就能自由定義它的行為實現(xiàn)AOP,所有這一切的橋梁就是InvocationHandler,它完成方法的攔截與代理。

代理模式一般要求代理類中有一個真正類(被代理類)的實例,在這里也就是Real的實例,這樣代理類才能去調(diào)用Real中原本的add方法。那Real在哪里呢?答案也是在InvocationHandler里。這與標準的代理模式相比,似乎多了一層嵌套,不過這并沒有關系,只要這個代理的鏈條能夠搭建起來,它就符合代理模式的要求。

注意到這里add方法的反射實例mAdd的初始化方式,我們使用靜態(tài)塊static {...}來完成,只會被設置一次,并且不會有多線程問題。當然你也可以用懶加載等方式,不過就得考慮并發(fā)的安全性。

最后看一下JDK Proxy的具體使用:

Handler handler = new Handler(new Real());
ifc p = (ifc)Proxy.newProxyInstance(ifc.class.getClassLoader(),
                                    new Class[] {ifc},
                                    handler);
p.add(1, 2);

方法newProxyInstance就會動態(tài)產(chǎn)生代理類,并且返回給我們一個實例,實現(xiàn)了ifc接口。這個方法需要三個參數(shù),第一個ClassLoader并不重要;第二個是接口列表,即這個代理類需要實現(xiàn)那些接口,因為JDK的Proxy是完全基于接口的,它封裝的是接口的方法而不是實體類;第三個參數(shù)就是InvocationHandler的實例,它會被放置在最終的代理類中,作為方法攔截和代理的橋梁。注意到這里的handler包含了一個Real實例,這在上面已經(jīng)說過是代理模式的必然要求。

總結一下JDK Proxy的原理,首先它是完全面向接口的,其實這才是符合代理模式的標準定義的。我們有兩個類,被代理類Real和需要動態(tài)生成的代理類ProxyClass,都實現(xiàn)了接口ifc。類ProxyClass需要攔截接口ifc上所有方法的調(diào)用,并且最終轉發(fā)到實體類Real上,這兩者之間的橋梁就是方法攔截器InvocatioHandlerinvoke方法。

上面的例子里我給出類ProxyClass的源代碼,當然實際上JDK Proxy是不會去產(chǎn)生源代碼的,而是直接生成類的原始數(shù)據(jù),它具體是怎么實現(xiàn)我們暫時不討論,我們目前只需要關心這個類是什么樣的,以及它實現(xiàn)代理的原理。

cglib實現(xiàn)動態(tài)代理

這是Spring使用的方式,與JDK Proxy不同之處在于它不是面向接口的,而是基于類的繼承。這似乎是有點違背代理模式的標準格式,不過這沒有關系,所謂的代理模式只是一種思想而不是嚴格的規(guī)范。我們直接看它是如何使用的。

現(xiàn)在沒有接口,我們直接有實體類:

class Real {
  public int add(int x, int y) {
    return x + y;
  }
}

類似于InvocationHandler,這里cglib直接使用一個叫MethodInterceptor的類,顧名思義。

public class Interceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object obj,
                          Method method,
                          Object[] args,
                          MethodProxy proxy) throws Throwable {
      System.out.println("=== BEFORE ===");
      Object re = proxy.invokeSuper(obj, args);
      System.out.println("=== AFTER ===");
      return re;
  }
}

使用方法:

public static void main(String[] args) {
  Enhancer eh = new Enhancer();
  eh.setSuperclass(Real.class);
  eh.setCallback(new Interceptor());

  Real r = (Real)eh.create();
  int result = r.add(1, 2);
}

如果你仔細和JDK Proxy比較,會發(fā)現(xiàn)它們其實是類似的:

首先JDK Proxy提供interface列表,而cglib提供superclass供代理類繼承,本質(zhì)上都是一樣的,就是提供這個代理類的簽名,也就是對外表現(xiàn)為什么類型。

然后是一個方法攔截器,JDK Proxy里是InvocationHandler,而cglib里一般就是MethodInterceptor,所有被代理的方法的調(diào)用是通過它們的invokeintercept方法進行轉接的,AOP的邏輯也是在這一層實現(xiàn)。

它們不同之處上面已經(jīng)說了,就在于cglib生成的動態(tài)代理類是直接繼承原始類的,所以我們這里也可以大概刻畫出這個代理類長什么樣子:

public ProxyClass extends Real {
  private static Method mAdd;
  private static MethodProxy mAddProxy;

  private MethodInterceptor interceptor;

  static {
    Class clazz = Class.forName("ifc");
    mAdd = clazz.getMethod("add", int.class, int.class);
    // Some logic to generate mAddProxy.
    // ...
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)interceptor.invoke(
        this, mAdd, new Object[] {x, y}, mAddProxy);
  }
}

因為直接繼承了Real,那自然就包含了Real的所有public方法,都通過interceptor.invoke進行攔截代理。這其實和上面JDK Proxy的原理是類似的,連invokeintercept方法的簽名都差不多,第一個參數(shù)是this指針代理類本身,第二個參數(shù)是方法的反射,第三個參數(shù)是方法調(diào)用的參數(shù)列表。唯一不同的是,這里多出一個MethodProxy,它是做什么用的?

如果你仔細看這里invoke方法內(nèi)部的寫法,當用戶想調(diào)用原始類(這里是Real)定義的方法時,它必須使用:

Object re = proxy.invokeSuper(obj, args);

這里就用到了那個MethodProxy,那我們?yōu)槭裁床恢苯訉懀?/p>

Object re = method.invoke(obj, args);

答案當然是不可以,你不妨試一下,程序會進入一個無限遞歸調(diào)用。這里的原因恰恰就是因為代理類是繼承了原始類的,obj指向的就是代理類對象的實例,所以如果你對它使用method.invoke,由于多態(tài)性,就會又去調(diào)用代理類的add方法,繼而又進入invoke方法,進入一個無限遞歸:

obj.add() {
  interceptor.invoke() {
    obj.add() {
      interceptor.invoke() {
        ...
      }
    }
  }
}

那我如何才能在interceptor.invoke()里去調(diào)用基類Realadd方法呢?當然通常做法是super.add(),然而這是在MethodInterceptor的方法里,而且這里的method調(diào)用必須通過反射完成,你并不能在語法層面上做到這一點。所以cglib封裝了一個類叫MethodProxy幫助你,這也是為什么那個方法的名字叫invokeSuper,表明它調(diào)用的是原始基類的真正方法。它究竟是怎么辦到的呢?你可以簡單理解為,動態(tài)代理類里會生成這樣一個方法:

int super_add(int x, int y) {
  return super.add(x, y);
}

當然你并不知道有這么一個方法,但invokeSuper會最終找到這個方法并調(diào)用,這都是在生成代理類時通過一系列反射的機制實現(xiàn)的,這里就不細展開了。

小結

以上我對比了JDK Proxycglib動態(tài)代理的使用方法和實現(xiàn)上的區(qū)別,它們本質(zhì)上是類似的,都是提供兩個最重要的東西:

接口列表或者基類,定義了代理類(當然也包括原始類)的簽名。

一個方法攔截器,完成方法的攔截和代理,是所有調(diào)用鏈的橋梁。

需要說明的一點是,以上我給出的代理類ProxyClass的源代碼,僅是參考性的最精簡版本,只是為了說明原理,而不是JDK Proxycglib真正生成的代理類的樣子,真正的代理類的邏輯要復雜的多,但是原理上基本是一致的。另外之前也說到過,事實上它們也不會生成源碼,而是直接產(chǎn)生類的字節(jié)碼,例如cglib是封裝了ASM來直接生成Class數(shù)據(jù)的。

如何生成代理類

接下來的部分純粹是實驗性質(zhì)的。既然知道了代理類長什么樣,可能還是有人會關心底層究竟如何在runtime動態(tài)生成這個類,這里我個人想了兩種方案。

第一種方法是動態(tài)生成ProxyClass源碼,然后動態(tài)編譯,就能得到Class了。這里就需要利用反射,加上一系列字符串拼接,生成源碼。如果你充分理解代理類應該長什么樣,其實并不是很難做到。那如何動態(tài)編譯呢?你可以使用JOOR,這是一個封裝了javax.tools.JavaCompiler的庫,幫助你方便地實現(xiàn)動態(tài)編譯Java源代碼。我試著寫了一個Demo,純粹是實驗性質(zhì)的。而且它有個重大問題,我不知道如何修改它編譯使用的classpath,在默認情況下它無法引用到你自己定義的任何類,因為它們不在編譯的classpath里,編譯就不會通過,這實際上就使得這個代碼生成器沒有任何卵用。。。我強行通過修改System.setPropertyclasspath來添加我的class路徑繞開了這個問題,然而這顯然不是個解決根本問題的方法。

第二種方法更直接,就是生成類的字節(jié)碼。這也是cglib使用的方法,它封裝了ASM,這是一個可以用來直接操縱Class數(shù)據(jù)的庫,通過它你就可以任意生成或修改你想要的Class,當然這需要你對虛擬機的字節(jié)碼比較了解,才能玩得通這種比較黑科技的套路。這里我也寫了一個Demo,也純粹是實驗而已,感興趣的童鞋也可以自己試一下。寫字節(jié)碼還是挺酸爽的,它類似匯編但其實比匯編容易的多。它不像匯編那樣一會兒寄存器一會兒內(nèi)存地址,一會兒堆一會兒棧,各種變量和地址繞來繞去。字節(jié)碼的執(zhí)行方式是很清晰的,變量都存儲在本地變量表里,棧只是用來做函數(shù)調(diào)用,所以非常直觀。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76511.html

相關文章

  • Java動態(tài)代理

    摘要:要明白,動態(tài)代理類的存在意義是為了攔截方法并修改邏輯而動態(tài)代理的局限性之一就是只能攔截接口所聲明的方法。因為動態(tài)代理類是繼承自業(yè)務類,所以該類和方法不能聲明成無法繼承或重寫。者最終都是生成了一個新的動態(tài)代理類對象。 動態(tài)代理 1、先談靜態(tài)代理 對于靜態(tài)代理,我們已經(jīng)很熟悉了。我們擁有一個抽象類,真實類繼承自抽象類并重寫其業(yè)務方法,代理類持有真實類的對象實例,在重寫業(yè)務方法中通過調(diào)用真實...

    CastlePeaK 評論0 收藏0
  • Java三種代理模式:靜態(tài)代理動態(tài)代理cglib代理

    摘要:動態(tài)代理又被稱為代理或接口代理。靜態(tài)代理在編譯時產(chǎn)生字節(jié)碼文件,可以直接使用,效率高。代理無需實現(xiàn)接口,通過生成類字節(jié)碼實現(xiàn)代理,比反射稍快,不存在性能問題,但會繼承目標對象,需要重寫方法,所以目標對象不能為類。 一、代理模式介紹 代理模式是一種設計模式,提供了對目標對象額外的訪問方式,即通過代理對象訪問目標對象,這樣可以在不修改原目標對象的前提下,提供額外的功能操作,擴展目標對象的功...

    Kaede 評論0 收藏0
  • java設計模式之動態(tài)代理

    摘要:場景描述病從口入這句成語告訴我們注意飲食健康,小六同學想吃蘋果,在吃蘋果之前需要清洗一下蘋果和洗一下手,吃完蘋果后,需要洗一下手保持個人衛(wèi)生十分鐘后。。。動態(tài)代理小六委托管家來代理洗食物和洗手,小六屬于委托對象,管家屬于代理對象。 前言 為了更好的理解代理模式,首先根據(jù)生活中實際場景進行模擬,讓我們在生活中去體驗設計思想的美妙。 場景描述 病從口入這句成語告訴我們注意飲食健康,小六同學...

    piglei 評論0 收藏0
  • Spring AOP就是這么簡單啦

    摘要:是一種特殊的增強切面切面由切點和增強通知組成,它既包括了橫切邏輯的定義也包括了連接點的定義。實際上,一個的實現(xiàn)被拆分到多個類中在中聲明切面我們知道注解很方便,但是,要想使用注解的方式使用就必須要有源碼因為我們要 前言 只有光頭才能變強 上一篇已經(jīng)講解了Spring IOC知識點一網(wǎng)打盡!,這篇主要是講解Spring的AOP模塊~ 之前我已經(jīng)寫過一篇關于AOP的文章了,那篇把比較重要的知...

    Jacendfeng 評論0 收藏0
  • 淺入淺出Java代理三種實現(xiàn)

    摘要:代理模式的實現(xiàn)靜態(tài)代理優(yōu)缺點優(yōu)點只對對需要的方法加代理邏輯。通過繼承的方式進行代理,無論目標對象有沒有實現(xiàn)接口都可以代理,但是無法處理的情況。 注意:本文所有的class使用的static修飾主要是為了能在一個類里面測試。實際項目中不應該這樣做的,應該分包分class。文字描述不是很多,還是看代碼比較好理解吧... 1. Java代理的理解 代理模式是一種設計模式,簡單說即是在不改變源...

    lewif 評論0 收藏0

發(fā)表評論

0條評論

h9911

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<