JAVA動態(tài)代理的實現(xiàn)原理

java動態(tài)代理主要是有Proxy和InvocationHandler兩個類實現(xiàn)的
一、我先來看下如何使用Proxy和InvocationHandler來實現(xiàn)動態(tài)代理
1.先定義一個接口MarkMan


image.png
  1. 再定義的一個類MarkManFactory實現(xiàn)MarkMan


    image.png
  2. 然后定義一個MyInvocationHandler實現(xiàn)InvocationHandler。
    在里面定義一個setFactory(Object obj)方法把要代理的對象傳入進去误堡,然后定義個newProxyInstance方法,方法里面調用Proxy.newProxyInstance()方法獲取代理對象奸绷。
    實現(xiàn)InvocationHandler的invoke方法


    image.png

    4.然后在main方法中進行調用


    image.png

    我們運行下程序设捐,結果打壓如下
    image.png

    二借浊、通過調試模式我們發(fā)現(xiàn),動態(tài)代理里萝招,代理類的類名是這樣的:
    [圖片上傳失敗...(image-9e5ea5-1613977035476)]

    這個代理類為何是這個名字蚂斤?它是如何執(zhí)行被代理對象的相關方法呢?我們在java文件編譯后的目錄里其實找不到這個名為$Proxy0的class文件的槐沼。帶著這個問題我來看看Proxy和InvocationHandler的源碼 看它們是如何實現(xiàn)動態(tài)代理的
    查看Proxy的newProxyInstance方法源碼

  @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
 InvocationHandler h)
        throws IllegalArgumentException {
        Objects.requireNonNull(h);
       ........
        /*
         * Look up or generate the designated proxy class.
         */
    //獲取clazz對象
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
          //獲取構造函數(shù)
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
          //通過構造函數(shù)new一個實例對象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

通過源碼我們可以看到是newProxyInstance是通過獲取class對象,然后通過class對象獲取構造函數(shù),通過構造函數(shù)new一個實例對象返回 接下來我們看下getProxyClass0(loader, intfs)方法

   private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

通過源碼 我們發(fā)現(xiàn)getProxyClass0就是從緩存中去獲取,繼續(xù)看WeakCache的get方法
這里我們主要看下subKeyFactory.apply(key, parameter)方法 ,其余都是驗證跟判斷

  public V get(K key, P parameter) {
  ....省略
     Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
     Supplier<V> supplier = valuesMap.get(subKey);
     Factory factory = null;
     while (true) {
            if (supplier != null) {
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    supplier = factory;
                }
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
  }

subKeyFactory是BiFunction接口,通過查看實現(xiàn)類發(fā)現(xiàn)是ProxyClassFactory類,接下來看ProxyClassFactory 的apply方法

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            ....省略
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            long num = nextUniqueNumber.getAndIncrement();
           ** //代理類的名稱  這里就是我們的要找的答案  $Proxy0 **
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }

通過源碼我們發(fā)現(xiàn)通過ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags) 轉換成byte[]數(shù)組,然后通過defineClass0方法轉換成Class字節(jié)碼返回,

 public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }
    //本地方法
    private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

為了查看byte[]數(shù)組里面的內(nèi)容我們可以自己定義個ProxyUtils工具類把byte[]寫到文件中,查看里面的內(nèi)容

public class ProxyUtils {
    public static void generateClassFile(Class clazz,String proxyName){
        /*ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);*/
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, new Class[]{clazz});
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(paths+proxyName+".class");
            out.write(proxyClassFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

運行代碼 我看到生成的文件是以Proxy0曙蒸、Proxy1這么的名稱
查看里面的內(nèi)容

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意這里是生成代理類的構造方法,方法參數(shù)為InvocationHandler類型岗钩,看到這纽窟,是不是就有點明白
  *為何代理對象調用方法都是執(zhí)行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
  *被代理對象的實例兼吓,不禁會想難道是....臂港? 沒錯,就是你想的那樣视搏。
  *
  *super(paramInvocationHandler)审孽,是調用父類Proxy的構造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler) throws {
    super(paramInvocationHandler);
  }
  
  //這個靜態(tài)塊本來是在最后的浑娜,我把它拿到前面來佑力,方便描述
   static{
    try {
      //看看這兒靜態(tài)塊兒里面有什么,是不是找到了saleMan方法筋遭。請記住saleMan通過反射得到的名字m3打颤,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {       
      Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.MarkManFactory").getMethod("saleMan", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException localNoSuchMethodException){
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }catch (ClassNotFoundException localClassNotFoundException){
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

  /**
  * 
  *這里調用代理對象的saleMan方法,直接就調用了InvocationHandler中的invoke方法漓滔,并把m3傳了進去瘸洛。
  *this.h.invoke(this, m3, null);這里簡單,明了次和。
  *來,再想想那伐,代理對象持有一個InvocationHandler對象踏施,InvocationHandler對象持有一個被代理的對象,
  *再聯(lián)系到InvacationHandler中的invoke方法罕邀。嗯畅形,就是這樣。
  */
  public final void saleMan() throws {
    try {
      this.h.invoke(this, m3, null);
      return;
    }catch (Error|RuntimeException localError){
      throw localError;
    }catch (Throwable localThrowable){
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  //注意诉探,這里為了節(jié)省篇幅日熬,省去了toString,hashCode肾胯、equals方法的內(nèi)容竖席。原理和saleMan方法一毛一樣耘纱。
}

這的h是什么呢 我們看下Proxy類可以看到


image.png

這個h的實例來自哪里?不就是我們在創(chuàng)建代理類的實例時傳入的嗎毕荐?


image.png

到此整個動態(tài)代理的源碼分析就結束了
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末束析,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子憎亚,更是在濱河造成了極大的恐慌员寇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件第美,死亡現(xiàn)場離奇詭異蝶锋,居然都是意外死亡,警方通過查閱死者的電腦和手機什往,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門扳缕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恶守,你說我怎么就攤上這事第献。” “怎么了兔港?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵庸毫,是天一觀的道長。 經(jīng)常有香客問我衫樊,道長飒赃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任科侈,我火速辦了婚禮载佳,結果婚禮上,老公的妹妹穿的比我還像新娘臀栈。我一直安慰自己蔫慧,他們只是感情好,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布权薯。 她就那樣靜靜地躺著姑躲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盟蚣。 梳的紋絲不亂的頭發(fā)上黍析,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機與錄音屎开,去河邊找鬼阐枣。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔼两。 我是一名探鬼主播甩鳄,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宪哩!你這毒婦竟也來了娩贷?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锁孟,失蹤者是張志新(化名)和其女友劉穎彬祖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體品抽,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡储笑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了圆恤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突倍。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盆昙,靈堂內(nèi)的尸體忽然破棺而出羽历,到底是詐尸還是另有隱情,我是刑警寧澤淡喜,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布秕磷,位于F島的核電站,受9級特大地震影響炼团,放射性物質發(fā)生泄漏澎嚣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一瘟芝、第九天 我趴在偏房一處隱蔽的房頂上張望易桃。 院中可真熱鬧,春花似錦锌俱、人聲如沸晤郑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贩汉。三九已至,卻和暖如春锚赤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褐鸥。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工线脚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓浑侥,卻偏偏與公主長得像姊舵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寓落,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容