<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)代理,那些面試中你容易忽略的細(xì)節(jié)!

          共 17462字,需瀏覽 35分鐘

           ·

          2021-03-29 11:52

          寫前言

          本來是打算把java代理模式給寫一下的,但是整理思路的時候發(fā)現(xiàn)這是一個龐大的工程,我需要講清楚什么是代理模式;它的應(yīng)用場景有哪些;代理又分為靜態(tài)代理和動態(tài)代理,它們分別是如何實現(xiàn)的,區(qū)別又是什么,我還要舉例,分析源碼,emm。顯然,我現(xiàn)在的時間安排是無法完成這個龐大的工程的,所以我就講一下目前解決問題中遇到的動態(tài)代理吧(默認(rèn)你大致了解代理模式)

          為什么要寫這篇文章

          我最近在學(xué)習(xí)Retrofit2源碼,而這個框架比較核心的一點就是動態(tài)代理,所以在這里把我學(xué)習(xí)過程中的一些我認(rèn)為比較關(guān)鍵的地方整理出來,分享給有需要的童鞋。

          Retrofit2的動態(tài)代理到底體現(xiàn)在哪里?請看下面代碼

          //retrofit的API接口對象
          ApiService apiService;
          //創(chuàng)建代理對象
           apiService = retrofit.create(ApiService.class);
          //調(diào)用代理類中的方法
          apiService.xxx();

          這是retrofit.create方法的源碼,很明顯的動態(tài)代理

          img

          動態(tài)代理的本質(zhì)是什么?

          我的理解:

          • 提供一個代理來控制對象的訪問;
          • 程序運行時動態(tài)生成代理類,這個代理類繼承于Proxy,并且實現(xiàn)自定義的委托類的接口;
          • 豐富原始類的操作

          動態(tài)代理的具體實現(xiàn)

          本來是不想寫一堆代碼來說動態(tài)代理的代碼是如何實現(xiàn)的,但是沒辦法,有些問題不通過舉例無法說清楚,下面一起來看一個簡單的例子吧:

          學(xué)生每學(xué)期都需要參加期末考試的

          public interface Student {
              //參加考試
              void exam();
          }

          學(xué)生分為很多專業(yè)的,其中計算機專業(yè)的學(xué)生需要參加計算機專業(yè)考試

          public class ComputerStudent implements Student {
              private String name;

              public ComputerStudent(String name) {
                  this.name = name;
              }

              @Override
              public void exam() {
                  System.out.println(name + " 參加計算機專業(yè)考試");
              }
          }

          由于每個學(xué)生的專業(yè)能力不一致,有的需要在考試前刷刷題,有的需要去找學(xué)霸輔導(dǎo)一下(抱大腿),等等,總之每個學(xué)生在考試前都需要做一些事情。

          如果現(xiàn)在需要定義很多不同特點的學(xué)生,你怎么做呢?如果去一個個定義不同的行為的話,那將是非常龐大的工作量,那有沒有簡單的辦法呢?答案是有的,通過代理類就可以實現(xiàn)。

          事實上我們沒有必要去定義每個學(xué)生,因為每個學(xué)生的行為是沒辦法確定的,我們可以通過動態(tài)代理在它做這個動作的時候去實現(xiàn)他的特定行為。

          public class Test {
              public static void main(String[] args){
                  //生成$Proxy0的class文件
                  //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");

                  //被代理類
                  final Student jack =new ComputerStudent("jack");

                  //生成代理對象
               Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                          jack.getClass().getInterfaces(),
                          new InvocationHandler() {
                              @Override
                              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                                  System.out.println("before exam do something");

                                  //通過反射調(diào)用對象的方法
                                  method.invoke(jack,args);

                                  System.out.println("after exam do something");

                                  return null;
                              }
                          });

                  //代理方法調(diào)用
                  jackProxy.exam();
              }
          }

          當(dāng)調(diào)用代理類的exam()方法,程序運行結(jié)果如下

          img

          這里有個重點,通過代理類對象jackProxy去調(diào)用方法和接口實現(xiàn)類對象jack去調(diào)用方法是有明顯區(qū)別的,通過代理方式去調(diào)用,可以在原來方法執(zhí)行前后做一些其它操作,這就是代理模式的特點

          那些你容易忽略的細(xì)節(jié)

          首先回顧一下動態(tài)代理的實現(xiàn)流程:

          • 1、通過Proxy.newProxyInstance方法創(chuàng)建一個代理對象;
          • 2、在內(nèi)部類InvocationHandlerinvoke()方法中做一些操作:利用反射調(diào)用被代理類(這里是ComputerStudent)中的方法,通常在這個調(diào)用方法前后還會做一些其它操作;
          • 3、代理對象jackProxy調(diào)用方法 對整個流程有個了解之后,下面來看一些細(xì)節(jié)問題。
          Q1:Proxy.newProxyInstance中的3個參數(shù)到底是什么?為什么要穿入這3個參數(shù)?

          系統(tǒng)接口定義如下:

          public static Object newProxyInstance(ClassLoader loader,
                                                    Class<?>[] interfaces,
                                                    InvocationHandler h)
          • loader :定義代理類的類加載器,這里要代理的是jack,所以用jack的類加載器
          • interfaces :是一個接口類的集合,具體來說是代理類實現(xiàn)的接口的集合,也是被代理類實現(xiàn)的接口的集合;
          • h :代理類對象調(diào)用方法時需要用到的一個接口對象,在系統(tǒng)生成的代理類內(nèi)部會用到它。

          到這里,我想細(xì)心的童鞋會想這個代理類到底是什么?似乎從頭到尾沒有露面過。的確是這樣,即使你去翻遍源碼你也找不到這個代理類,因為在動態(tài)代理模式中它是在運行時生成的,所以你在源碼甚至.class中都找不到他的影子。

          我先說結(jié)論:運行時動態(tài)生成的代理類叫做$Proxy0,關(guān)于這個類怎么看源碼,很多介紹代理的文章都沒有說清楚,讀者也是一臉懵逼;如果你想要看它的內(nèi)容,可以通過如下方法

          public class Test {
              public static void main(String[] args){
                  //生成$Proxy0的class文件
                  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");

                  //被代理類
               Student jack =new ComputerStudent("jack");
                  //生成代理對象
               Student jackProxy= (Student) Proxy.newProxyInstance();
          }

          在運行方法中添加這一行代碼,在運行后會自動在項目根目錄生成com文件夾,其中...\IdeaProjects\com\sun\proxy下會生成$Proxy0.class文件。注意:我是在IDEA上調(diào)試成功的,我在Android Studio上測試是沒有生成的。暫時不知道原理,有了解的大佬可以科普一下。System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

          代理類$Proxy0源碼如下:

          package com.sun.proxy;

          import java.lang.reflect.InvocationHandler;
          import java.lang.reflect.Method;
          import java.lang.reflect.Proxy;
          import java.lang.reflect.UndeclaredThrowableException;
          import student.Student;

          public final class $Proxy0 extends Proxy implements Student {
              private static Method m1;
              private static Method m3;
              private static Method m2;
              private static Method m0;

              public $Proxy0(InvocationHandler var1) throws  {
                  super(var1);
              }

              public final boolean equals(Object var1) throws  {
                  try {
                      return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
                  } catch (RuntimeException | Error var3) {
                      throw var3;
                  } catch (Throwable var4) {
                      throw new UndeclaredThrowableException(var4);
                  }
              }

              public final void exam() throws  {
                  try {
                      super.h.invoke(this, m3, (Object[])null);
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

              public final String toString() throws  {
                  try {
                      return (String)super.h.invoke(this, m2, (Object[])null);
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

              public final int hashCode() throws  {
                  try {
                      return (Integer)super.h.invoke(this, m0, (Object[])null);
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

              static {
                  try {
                      m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                      m3 = Class.forName("student.Student").getMethod("exam");
                      m2 = Class.forName("java.lang.Object").getMethod("toString");
                      m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                  } catch (NoSuchMethodException var2) {
                      throw new NoSuchMethodError(var2.getMessage());
                  } catch (ClassNotFoundException var3) {
                      throw new NoClassDefFoundError(var3.getMessage());
                  }
              }
          }

          關(guān)于$Proxy0先說幾個結(jié)論:

          • $Proxy0 繼承了Proxy,實現(xiàn)了自定義的目標(biāo)接口Student;
          • $Proxy0定義了接口Student中的方法exam(),以及Object對象的幾個方法equals()、toString()、hashCode()
          • 構(gòu)造方法中傳了InvocationHandler對象,并且在定義的方法中調(diào)用了它的invoke()方法

          回到剛才的問題,Proxy.newProxyInstance為什么要穿入這3個參數(shù)?

                  //生成代理對象
               Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                          jack.getClass().getInterfaces(),
                          new InvocationHandler() );

          因為代理對象是基于自定義接口Student和jack類加載器代理出來的。

          Q2:InvocationHandler的作用及其invoke()方法等解釋

          當(dāng)代理對象調(diào)用方法時,會回調(diào)執(zhí)行剛才new出來的InvocationHandler中的invoke()方法。

          關(guān)于invoke()方法,在看了源碼和反復(fù)代碼驗證之后,我做出的解釋如下:

           /**
               * 這個方法不是我們顯示的去調(diào)用,是系統(tǒng)生成的代理類$Proxy0中調(diào)用的
               *
               * @param proxy  代理對象:也就是Proxy.newProxyInstance返回的對象
               * @param method 要調(diào)用的方法
               * @param args   調(diào)用方法的參數(shù)
               * @return
               * @throws Throwable
               */
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}

          注意: invoke()不是顯示調(diào)用的,是在代理類中去調(diào)用的。比如調(diào)用exam()方法時, 該方法中會調(diào)用super.h.invoke(this, m3, null);,就是調(diào)用父類的h的invoke(),它的父類是Proxy,h是一個InvocationHandler對象;所以說當(dāng)調(diào)用exam()方法時最后回調(diào)到剛才new出來的InvocationHandler的invoke方法。

          public final void exam() throws  {
                  try {
                      super.h.invoke(this, m3, (Object[])null);
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

          下面再來看一個問題

          public class Test {
              public static void main(String[] args){
                  //生成$Proxy0的class文件
                  //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");

                  //被代理類
                  final Student jack =new ComputerStudent("jack");

                  //生成代理對象
               Student jackProxy= (Student) Proxy.newProxyInstance(jack.getClass().getClassLoader(),
                          jack.getClass().getInterfaces(),
                          new InvocationHandler() {
                              @Override
                              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                                  System.out.println("before exam do something");

                                  //通過反射調(diào)用對象的方法
                                  method.invoke(jack,args);

                                  System.out.println("after exam do something");

                                  return null;
                              }
                          });

                  //方法調(diào)用
                  jackProxy.exam();
              }
          }

          看一下InvocationHandler中invoke()方法內(nèi)部的調(diào)用

            //通過反射調(diào)用對象的方法
            method.invoke(jack,args);

          這句代碼的作用是通過反射調(diào)用一個方法,如果把實現(xiàn)類對象jack換成代理類對象proxy會發(fā)生什么?

          結(jié)論:會循環(huán)報錯,停不下來那種。因為proxy是代理實例,也就是這里的jackProxy,當(dāng)這個對象的方法被調(diào)用的時候會觸發(fā)InvocationHandler中invoke()方法,而InvocationHandler內(nèi)部又再次調(diào)用proxy的方法,如此不停循環(huán)。

          動態(tài)代理的使用場景

          優(yōu)點:在運行時切入原始類,改變類的方法,這樣可以豐富該方法的操作,比如在方法之前、之后做一些其它操作。

          應(yīng)用的話,比如Retrofit框架、AOP(面向切面編程)等等。

          作者:嘮嗑008
          來源:https://www.jianshu.com/p/b3e67ba70b51

          推薦閱讀

          這么美好的夜晚,搞點不一樣的!

          從零搭建SpringCloud服務(wù)(史上最詳細(xì))

          公司高管寫出低級Bug,導(dǎo)致公司70GB數(shù)據(jù)泄露

          955 互聯(lián)網(wǎng)公司白名單來了!這些公司月薪20k,沒有996!福利榜國內(nèi)大廠只有這家!

          ?一個完整的、全面k8s化的集群穩(wěn)定架構(gòu)(值得借鑒)

          ? 2020年國內(nèi)互聯(lián)網(wǎng)公司的薪酬排名!

          ?  基于SpringBoot 的CMS系統(tǒng),拿去開發(fā)企業(yè)官網(wǎng)真香

           深度介紹分布式系統(tǒng)原理與設(shè)計

           程序員因違反竟業(yè)協(xié)議,賠騰訊97.6萬...

          阿里一面:如何保證API接口數(shù)據(jù)安全?

          徒手?jǐn)]了一個RPC框架,理解更透徹了,代碼已上傳github,自取~

          一個完整的外賣系統(tǒng)




          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  狠狠干免费的成人视频 | 亚洲无码 国产精品 豆花 | 日韩av在线网 | 97精品在线视频 | 自拍超碰人人人人 |