<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java動態(tài)代理和Cglib動態(tài)代理最強(qiáng)王者陣容

          共 9384字,需瀏覽 19分鐘

           ·

          2021-05-24 18:13

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


          作者丨黎杜
          來源丨黎杜編程


          前言

          動態(tài)代理包括「jdk的動態(tài)代理」「cglib 的動態(tài)代理」,兩者實(shí)現(xiàn)相同的功能,但是實(shí)現(xiàn)方式卻是有明顯的區(qū)別。

          下面我們就通過代碼的方式層層的深入這兩種動態(tài)代理,了解他們的性能、底層的實(shí)現(xiàn)原理以及應(yīng)用場景。

          代理模式

          在詳細(xì)介紹動態(tài)代理之前,先來說說Java中的代理模式。代理模式分為兩種:

          1. 「靜態(tài)代理」:也就是23種設(shè)計(jì)模式中的代理模式,由程序員自己編寫源代碼并進(jìn)行編譯,在程序運(yùn)行之前已經(jīng)編譯好了.class文件。
          2. 「動態(tài)代理」:包括jdk的動態(tài)代理和cglib的動態(tài)代理,運(yùn)行時通過反射動態(tài)創(chuàng)建。

          代理模式定義:我的個人理解就是給某一個對象提供一個代理對象,在代理對象中擁有被代理對象的引用,并在代理對象中調(diào)用被代理對象的方法之前和之后進(jìn)行方法的增強(qiáng)。

          我這里畫了一張代理模式的類圖,設(shè)計(jì)模式中的代理模式比較簡單,代理類和委托類有公共的接口,最后由代理類去執(zhí)行委托類的方法:

          代理模式就好像生活中的中介,去幫你做事,而不用自己去做事。舉個例子,比如你要買車,但是買車之前你要到處找車源,找到車源給錢了還要辦理一堆手續(xù)。

          (1)下面我們以買車這個案例進(jìn)行代理模式的代碼編寫,首先要有一個公共的接口Person,Person接口里面定義公共的方法:

          public interface Person{
          void buyCar();
          }

          (2)然后定義一個委托類,也就是我本人Myself,并實(shí)現(xiàn)Person接口,具體代碼如下:

          public class Myself implements Person {

          @Override
          public void buyCar() {
          System.out.println("我要買車了");
          }
          }

          (3)最后就是創(chuàng)建代理類CarProxy,同樣也是實(shí)現(xiàn)Person接口,具體實(shí)現(xiàn)代碼如下:

          public class CarProxy implements Person{

          private Myself myself ;

          public CarProxy(final Myself myself ) {
          this.myself = myself ;
          }

          @Override
          public void buyCar() {
          System.out.println("買車前去找車源");
          myself .buyCar();
          System.out.println("買車后辦理手續(xù)");
          }
          }

          這個代理的demo很簡單,如上面的類圖所示,代理類和委托類都實(shí)現(xiàn)公共的接口Person,在委托類中進(jìn)行方法的具體業(yè)務(wù)邏輯的實(shí)現(xiàn),而代理類中再次對這個方法進(jìn)行增強(qiáng)。

          「代理模式」的優(yōu)點(diǎn)就是「能夠?qū)δ繕?biāo)對象進(jìn)行功能的擴(kuò)展」,缺點(diǎn)是每一個業(yè)務(wù)類都要創(chuàng)建一個代理類,這樣會「使我們系統(tǒng)內(nèi)的類的規(guī)模變得很大,不利于維護(hù)」

          于是就出現(xiàn)了動態(tài)代理,仔細(xì)思考靜態(tài)代理的缺點(diǎn),就是一個委托類就會對象一個代理類,那么是否可以將代理類做成一個通用的呢?

          我們仔細(xì)來看一下下面的這個圖:

          我們把靜態(tài)代理所有的執(zhí)行過程都可以抽象成這張圖的執(zhí)行過程,Proxy角色無非是在「調(diào)用委托類處理業(yè)務(wù)的方法之前或者之后做一些額外的操作」

          那么為了做一個通用性的處理,就把調(diào)用委托類的method的動作抽出來,看成一個通用性的處理類,于是就有了InvocationHandler角色,抽象成一個處理類。

          這樣在Proxy和委托類之間就多了一個InvocationHandler處理類的角色,這個角色主要是「將之前代理類調(diào)用委托類的方法的動作進(jìn)行統(tǒng)一的調(diào)用,都由InvocationHandler來處理」

          于是之前上面的類圖就有了這樣的改變,在Proxy和委托類之間加入了InvocationHandler,具體的實(shí)現(xiàn)圖如下:

          看完上面的圖似乎有那么一點(diǎn)點(diǎn)的理解,下面我們就來詳細(xì)的深入動態(tài)代理。

          jdk動態(tài)代理

          上面講解到動態(tài)代理是在運(yùn)行時環(huán)境動態(tài)加載class文件,并創(chuàng)建對應(yīng)的class對象,那么動態(tài)代理和靜態(tài)代理的執(zhí)行時機(jī)是在哪里呢?

          我這邊又畫了一張?jiān)韴D,感覺我為畫圖操碎了心,每一個點(diǎn)都會畫一個截圖,是不是很暖。

          這個是靜態(tài)代理的運(yùn)行原理圖,靜態(tài)代理在程序運(yùn)行時就已經(jīng)創(chuàng)建好了class文件,在程序啟動后的某一個時機(jī)(用到class文件)就會加載class文件到內(nèi)存中。

          當(dāng)在運(yùn)行時期動態(tài)生成class文件并加載class文件的運(yùn)行原理圖如下:

          在JVM運(yùn)行期時遵循JVM字節(jié)碼的結(jié)構(gòu)和規(guī)范生成二進(jìn)制文件,并加載到內(nèi)存中生成對應(yīng)的Class對象。這樣,就完成了動態(tài)創(chuàng)建class文件和Class對象的功能了。

          在jdk的動態(tài)代理中的「Proxy類和委托類要求實(shí)現(xiàn)相同的功能」,這里的相同是指「他們都可以調(diào)用統(tǒng)一的邏輯業(yè)務(wù)方法」。要實(shí)現(xiàn)這樣的設(shè)計(jì)有以下三種方法:

          1. 「實(shí)現(xiàn)同一個接口」:接口里面定義公共的方法。
          2. 「繼承」:Proxy繼承委托類,這樣Proxy就有了和委托類一樣的功能,或者兩者都繼承同一個類,把公共實(shí)現(xiàn)業(yè)務(wù)邏輯的方法放在父類中,這樣也能實(shí)現(xiàn)。
          3. 「兩者內(nèi)部都有同一個類的引用」:這個和繼承有異曲同工之妙,都可以統(tǒng)一的調(diào)用統(tǒng)一的業(yè)務(wù)邏輯方法。

          在jdk的動態(tài)代理中,是采用第一種方法進(jìn)行實(shí)現(xiàn),必須有公共的接口,下面我們還是通過靜態(tài)代理的案例使用動態(tài)代理來實(shí)現(xiàn)。

          (1)首先創(chuàng)建一個公共接口Person:

          public interface Person{
          void buyCar();
          }

          (2)然后創(chuàng)建接口的實(shí)現(xiàn)類Myself:

          public class Myself implements Person {

          @Override
          public void buyCar() {
          System.out.println("我要買車了");
          }
          }

          (3)這一步就是比較關(guān)鍵的,要創(chuàng)建一個類并實(shí)現(xiàn)InvocationHandler

          public class InvocationHandlerImpl implements InvocationHandler {

          private Person person;

          public InvocationHandlerImpl(Person person){
          this.person=person;
          }

          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          System.out.println("買車前開始找車源。。。。");
          method.invoke(person, args);
          System.out.println("買車后辦理手續(xù)。。。。");
          return null;
          }
          }

          (4)最后一步就是進(jìn)行測試:

          public class Test {

          public static void main(String[] args) {
          Myself myself= new Myself();
          // 創(chuàng)建代理對象,這里有三個參數(shù),第一個是類的ClassLoader,第二個是該類的接口集合,第三個就是InvocationHandler
          Object o = Proxy.newProxyInstance(myself.getClass().getClassLoader(), myself.getClass().getInterfaces(), new InvocationHandlerImpl(myself));
          Person person= (Person) o;
          person.buyCar();
          }
          }

          整體來說jdk動態(tài)代理的應(yīng)用過程還是比較簡單的,重要的實(shí)現(xiàn)理解他的底層實(shí)現(xiàn)過程,它的重要實(shí)現(xiàn)步驟就是InvocationHandler中 的invoke方法處理。

          invoke方法才是實(shí)現(xiàn)方法的調(diào)用者,根據(jù)上面的參數(shù)最后才會創(chuàng)建代理對象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

          那么在實(shí)現(xiàn)jdk動態(tài)代理的過程都做了哪些工作呢?具體有以下6個步驟:

          1. 獲取委托類也就是Myself上的所有接口。
          2. 生成代理,生成的代理的名稱也是有規(guī)律的,一般是在「com.sun.proxy.$ProxyXXX」
          3. 動態(tài)創(chuàng)建代理類的字節(jié)碼信息,也就是class文件。
          4. 根據(jù)class文件創(chuàng)建Class對象。
          5. 創(chuàng)建自己的InvocationHandler并實(shí)現(xiàn)InvocationHandler重寫invoke方法,實(shí)現(xiàn)對委托類方法的調(diào)用和增強(qiáng)。
          6. 最后是代理對象的創(chuàng)建,并調(diào)用方法,實(shí)現(xiàn)代理的功能。

          我們可以通過反編譯工具來看看生成的代理類的源碼是怎么樣的,我這里使用的反編譯工具是jd-gui,推薦給大家。

           public final class MyselfProxy extends Proxy  implements Person  {
          private static Method m1;
          private static Method m3;
          private static Method m0;
          private static Method m2;

          public MyselfProxy(InvocationHandler paramInvocationHandler) throws {
          super(paramInvocationHandler);
          }

          public final boolean equals(Object paramObject) throws {
          try { // InvocationHandler 實(shí)現(xiàn)equals的調(diào)用
          return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
          } catch (Error|RuntimeException localError) {
          throw localError;
          } catch (Throwable localThrowable) {
          throw new UndeclaredThrowableException(localThrowable);
          }
          }

          public final void buyCar() throws {
          try {
          // InvocationHandler實(shí)現(xiàn)buyCar的調(diào)用
          this.h.invoke(this, m3, null);
          return;
          } catch (Error|RuntimeException localError) {
          throw localError;
          } catch (Throwable localThrowable) {
          throw new UndeclaredThrowableException(localThrowable);
          }
          }

          public final int hashCode() throws {
          try {
          // InvocationHandler實(shí)現(xiàn)hashCode方法的調(diào)用
          return ((Integer)this.h.invoke(this, m0, null)).intValue();
          }
          catch (Error|RuntimeException localError)
          {
          throw localError;
          }
          catch (Throwable localThrowable)
          {
          throw new UndeclaredThrowableException(localThrowable);
          }
          }

          public final String toString() throws {
          try {
          // InvocationHandler實(shí)現(xiàn)toString的調(diào)用
          return (String)this.h.invoke(this, m2, null);
          } catch (Error|RuntimeException localError) {
          throw localError;
          } catch (Throwable localThrowable) {
          throw new UndeclaredThrowableException(localThrowable);
          }
          }

          static {
          try { //在靜態(tài)塊中通過反射初始化函數(shù)
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("com.ldc.org.Person").getMethod("buyCar", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          return;
          } catch (NoSuchMethodException localNoSuchMethodException) {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
          } catch (ClassNotFoundException localClassNotFoundException) {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
          }
          }
          }

          從上面反編譯的源碼中可以可以看出,在「靜態(tài)塊中直接通過反射的方式來生成Method對象」,對方法的調(diào)用則是通過InvocationHandler對象來進(jìn)行調(diào)用。

          仔細(xì)的總結(jié)可以看出上面反編譯出來的代理類有以下特征:

          1. 繼承 「java.lang.reflect.Proxy」類,并實(shí)現(xiàn)統(tǒng)一的接口Person。
          2. 所有的方法都是「final」修飾的。
          3. 都是通過「InvocationHandler」對象執(zhí)行invoke方法的調(diào)用統(tǒng)一調(diào)用函數(shù),invoke方法通過Method參數(shù)來區(qū)分是什么方法,進(jìn)而相應(yīng)的處理。

          到這里我想大家應(yīng)該對jdk的動態(tài)代理有一個清晰的認(rèn)識了,包括他的底層實(shí)現(xiàn)的原理,下面我們就來詳細(xì)的了解cglib動態(tài)代理的是實(shí)現(xiàn)方式。

          cglib動態(tài)代理

          在實(shí)現(xiàn)jdk的動態(tài)代理的實(shí)現(xiàn)會發(fā)現(xiàn),「jdk動態(tài)代理必須實(shí)現(xiàn)一個接口」,并且代理類也「只能代理接口中實(shí)現(xiàn)的方法」,要是實(shí)現(xiàn)類中有自己私有的方法,而接口中沒有的話,該方法不能進(jìn)行代理調(diào)用。

          基于這種情況cglib便出現(xiàn)了,他也可以在運(yùn)行期擴(kuò)展Java類和Java接口。

          cglib底層是采用「字節(jié)碼技術(shù)」,其原理是通過字節(jié)碼技術(shù)生成一個子類,并在子類中攔截父類的方法的調(diào)用,織入業(yè)務(wù)邏輯。

          因?yàn)樵硎遣捎美^承的方式,所以被代理的類不能被final修飾,在Spring Aop中底層的實(shí)現(xiàn)是以這兩種動態(tài)代理作為基礎(chǔ)進(jìn)行實(shí)現(xiàn)。

          當(dāng)使用cglib動態(tài)代理一個類demo時,JVM又做了哪些工作呢?

          1. 「首先找到demo類中的所有非final的公共方法。」
          2. 「然后將這些方法轉(zhuǎn)化為字節(jié)碼。」
          3. 「通過這些字節(jié)碼轉(zhuǎn)化為Class對象。」
          4. 「最后由MethodInterceptor實(shí)現(xiàn)代理類中所有方法的調(diào)用。」

          (1)那么我們通過代碼也來實(shí)現(xiàn)cglib動態(tài)代理,還是創(chuàng)建Myself類,但是此時不需要實(shí)現(xiàn)接口:

          public class Myself {

          @Override
          public void buyCar() {
          System.out.println("I'm going to buy a house");
          }
          }

          (2)然后是創(chuàng)建MyMethodInterceptor類實(shí)現(xiàn)MethodInterceptor接口,這個和動態(tài)代理實(shí)現(xiàn)InvocationHandler方式一樣,實(shí)現(xiàn)統(tǒng)一方法的調(diào)用。

          public class MyMethodInterceptor implements MethodInterceptor {
          @Override
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
          System.out.println("買車前開始找車源。。。。");
          proxy.invokeSuper(obj, args);
          System.out.println("買車后辦理手續(xù)。。。。");
          return null;
          }

          }

          (3)最后是進(jìn)行測試

          public class Test {
          public static void main(String[] args) {
          Myself myself= new Myself();
          MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor ();
          //cglib 中加強(qiáng)器,用來創(chuàng)建動態(tài)代理
          Enhancer enhancer = new Enhancer();
          //設(shè)置要創(chuàng)建的代理類
          enhancer.setSuperclass(myself.getClass());
          // 設(shè)置回調(diào),這里相當(dāng)于是對于代理類上所有方法的調(diào)用
          enhancer.setCallback(myMethodInterceptor );
          // 創(chuàng)建代理類
          Myself proxy =(Myself)enhancer.create();
          proxy.buyCar();
          }
          }

          總結(jié)來說cglib是一個強(qiáng)大的、高性能的Code生產(chǎn)類庫,在Spring中就是通過cglib方式繼承要被代理的類,重寫父類的方法,實(shí)現(xiàn)Aop編程。

          cglib創(chuàng)建動態(tài)代理對象的性能時機(jī)要比jdk動態(tài)代理的方式高很多,但是創(chuàng)建對象所花的時間卻要比jdk動態(tài)代理方式多很多。

          在應(yīng)用方面單例模式更適合用cglib,無需頻繁的創(chuàng)建對象,相反,則使用jdk動態(tài)代理的方式更加合適。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 26
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一级操逼网 | 国产黄色中文字幕 | 免费无码一区二区三区四区五区 | 午夜爽爽| 国内精品小视频 |