成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

h9911 / 859人閱讀

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

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

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

文章的最后也會(huì)探討如何自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的動(dòng)態(tài)代理,并提供我自己實(shí)現(xiàn)的簡(jiǎn)單版本,當(dāng)然僅供參考。

JDK Proxy

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

定義接口:

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

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

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

Real就是我們需要代理的類,比如我們希望在調(diào)用add的前后打印一些log,這實(shí)際上就是AOP了。我們需要最終產(chǎn)生一個(gè)代理類,實(shí)現(xiàn)同樣的接口ifc,執(zhí)行Real.add的功能,但需要增加一行新的打印語(yǔ)句。這一切對(duì)用戶是透明的,用戶只需要關(guān)心接口的調(diào)用。為了能在Real.add的周圍添加額外代碼,動(dòng)態(tài)代理都是通過(guò)一種類似方法攔截器的東西來(lái)實(shí)現(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;
  }
}

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

所以我們應(yīng)該能大概刻畫(huà)出這個(gè)代理類的模樣:

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});
  }
}

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

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

注意到這里add方法的反射實(shí)例mAdd的初始化方式,我們使用靜態(tài)塊static {...}來(lái)完成,只會(huì)被設(shè)置一次,并且不會(huì)有多線程問(wèn)題。當(dāng)然你也可以用懶加載等方式,不過(guò)就得考慮并發(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就會(huì)動(dòng)態(tài)產(chǎn)生代理類,并且返回給我們一個(gè)實(shí)例,實(shí)現(xiàn)了ifc接口。這個(gè)方法需要三個(gè)參數(shù),第一個(gè)ClassLoader并不重要;第二個(gè)是接口列表,即這個(gè)代理類需要實(shí)現(xiàn)那些接口,因?yàn)镴DK的Proxy是完全基于接口的,它封裝的是接口的方法而不是實(shí)體類;第三個(gè)參數(shù)就是InvocationHandler的實(shí)例,它會(huì)被放置在最終的代理類中,作為方法攔截和代理的橋梁。注意到這里的handler包含了一個(gè)Real實(shí)例,這在上面已經(jīng)說(shuō)過(guò)是代理模式的必然要求。

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

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

cglib實(shí)現(xiàn)動(dòng)態(tài)代理

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

現(xiàn)在沒(méi)有接口,我們直接有實(shí)體類:

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

類似于InvocationHandler,這里cglib直接使用一個(gè)叫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);
}

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

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

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

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

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);
  }
}

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

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

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

這里就用到了那個(gè)MethodProxy,那我們?yōu)槭裁床恢苯訉?xiě):

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

答案當(dāng)然是不可以,你不妨試一下,程序會(huì)進(jìn)入一個(gè)無(wú)限遞歸調(diào)用。這里的原因恰恰就是因?yàn)榇眍愂?strong>繼承了原始類的,obj指向的就是代理類對(duì)象的實(shí)例,所以如果你對(duì)它使用method.invoke,由于多態(tài)性,就會(huì)又去調(diào)用代理類的add方法,繼而又進(jìn)入invoke方法,進(jìn)入一個(gè)無(wú)限遞歸:

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

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

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

當(dāng)然你并不知道有這么一個(gè)方法,但invokeSuper會(huì)最終找到這個(gè)方法并調(diào)用,這都是在生成代理類時(shí)通過(guò)一系列反射的機(jī)制實(shí)現(xiàn)的,這里就不細(xì)展開(kāi)了。

小結(jié)

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

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

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

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

如何生成代理類

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

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76511.html

相關(guān)文章

  • Java動(dòng)態(tài)代理

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

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

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

    Kaede 評(píng)論0 收藏0
  • java設(shè)計(jì)模式之動(dòng)態(tài)代理

    摘要:場(chǎng)景描述病從口入這句成語(yǔ)告訴我們注意飲食健康,小六同學(xué)想吃蘋(píng)果,在吃蘋(píng)果之前需要清洗一下蘋(píng)果和洗一下手,吃完蘋(píng)果后,需要洗一下手保持個(gè)人衛(wèi)生十分鐘后。。。動(dòng)態(tài)代理小六委托管家來(lái)代理洗食物和洗手,小六屬于委托對(duì)象,管家屬于代理對(duì)象。 前言 為了更好的理解代理模式,首先根據(jù)生活中實(shí)際場(chǎng)景進(jìn)行模擬,讓我們?cè)谏钪腥ンw驗(yàn)設(shè)計(jì)思想的美妙。 場(chǎng)景描述 病從口入這句成語(yǔ)告訴我們注意飲食健康,小六同學(xué)...

    piglei 評(píng)論0 收藏0
  • Spring AOP就是這么簡(jiǎn)單啦

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

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

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

    lewif 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<