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

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

代理模式

代理模式強(qiáng)調(diào)在對(duì)被代理對(duì)象的控制。代理模式知識(shí)點(diǎn)不做贅述。

靜態(tài)代理,代理類的代碼是在編譯期間就已經(jīng)確定好的。

動(dòng)態(tài)代理,代理類的代碼編譯期間是沒(méi)有的,只有在運(yùn)行期間才能確定。

簡(jiǎn)單回顧下靜態(tài)代理相關(guān)代碼,以統(tǒng)計(jì)方法耗時(shí)為例。

//聲明接口類
public interface IDoSth {
    void doSth(String s);
}
//聲明實(shí)際類
public class DoSth implements IDoSth{
    @Override
    public void doSth(String s) {
        System.out.println("doSth:" + s);
    }
}
//聲明靜態(tài)代理類,可對(duì)被代理的對(duì)象進(jìn)行定制化處理,此處將 xx 掉包
public class ProxyDoSth implements IDoSth{
    private IDoSth instance;
    public ProxyDoSth(IDoSth ins) {
        this.instance = ins;
    }
    @Override
    public void doSth(String s) {
        long start = System.currentTimeMillis();
        instance.doSth(s);
        System.out.println(System.currentTimeMillis()-start);
    }
}

動(dòng)態(tài)代理相關(guān)語(yǔ)法

// 動(dòng)態(tài)代理相關(guān)代碼
        IDoSth ins = new DoSth();
      IDoSth proxy = (IDoSth) Proxy.newProxyInstance(Test.class.getClassLoader(), ins.getClass().getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long start = System.currentTimeMillis();
            Object res = method.invoke(ins, args);
            System.out.println("method: " + method.getName() + " costTime : "
                + (System.currentTimeMillis() - start));
            return res;
        }
      });
        proxy.doSth("做飯");
        // 打印 proxy 對(duì)象相關(guān)類和接口信息
        System.out.println(proxy.getClass()); 
      System.out.println(proxy.getClass().getSuperclass());
      for (Class cls : proxy.getClass().getInterfaces()) {
            System.out.println(cls);
      }
}
//輸出結(jié)果
doSth:做飯
method: doSth costTime : 1 // 方法耗時(shí)
class com.sun.proxy.$Proxy0 //proxy 類的類名
class java.lang.reflect.Proxy //proxy 類父類
interface jjava.dynamicProxy.IDoSth //proxy 類實(shí)現(xiàn)的接口

由 proxy 對(duì)象可知,其類名com.sun.proxy.$Proxy0 ,該類父類為 java.lang.reflect.Proxy且實(shí)現(xiàn)了 IDoSth 接口。

這里面有兩個(gè)關(guān)鍵類,一個(gè)是 Proxy,一個(gè)是InvocationHandler。Proxy 主要負(fù)責(zé)代理類的生成,InvocationHandler 主要負(fù)責(zé)用戶自定義實(shí)現(xiàn)代理類的功能。

動(dòng)態(tài)代理的本質(zhì)

動(dòng)態(tài)代理沒(méi)那么玄乎,僅僅是在程序運(yùn)行期間生成了代理類的代碼而已。那這份代碼長(zhǎng)什么樣子呢?

我們將動(dòng)態(tài)生成的 Proxy 類的代碼保存在class 文件中,這一步代碼如下

//$Proxy0 為生成的代理類的名字,path 為保存的文件名,實(shí)際上就是將byte 數(shù)組保存到本地,newProxyInstance() 方法內(nèi)部就是通過(guò)這種方式生成的 代理類的 byte[]
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", ins.getClass().getInterfaces());
      String path = "/${projectPath}/jjava/dynamicProxy/$Proxy0.class";
      try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("cls 文件寫入成功");
      } catch (Exception e) {
            System.out.println("cls 文件寫入失敗");
            e.printStackTrace();
      }

生成的 $Proxy0 類的代碼如下

public final class $Proxy0 extends Proxy implements IDoSth {
    private static Method m1;   //java.lang.Object#equals()
    private static Method m2;   //java.lang.Object#toString()
    private static Method m3;   //jjava.dynamicProxy.IDoSth#doSth()
    private static Method m0;   //java.lang.Object#hashcode()
    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);
        }
    }
        // 通過(guò) proxy.doSth(xx) 時(shí),本質(zhì)上調(diào)的是這個(gè)方法,內(nèi)部是通過(guò)調(diào)用 InvocationHandler 中的 invoke 方法實(shí)現(xiàn)的。
    public final void doSth(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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("jjava.dynamicProxy.IDoSth").getMethod("doSth", Class.forName("java.lang.String"));
            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ò)類信息,可以和簽名的log信息互相驗(yàn)證。同時(shí) 由于所有動(dòng)態(tài)代理產(chǎn)生的類都是Proxy 的子類,所以我們?cè)谑褂脮r(shí),只能用 interfaces 中的任一接口承接,而不能用自定義的其他類型,否則會(huì)報(bào) ClassCastException。

可以看到 $Proxy0類的構(gòu)造函數(shù),包括一個(gè) InvocationHandler 類型的參數(shù),并將該參數(shù)傳給了父類的函數(shù),該闡述就是我們調(diào)用Proxy.newProxyInstance() 方法傳入的 h。

我們?cè)谡{(diào)用代理類的方法時(shí),如 proxy.doSth(),本質(zhì)上是通過(guò)super.h.invoke(this, m3, new Object[]{var1}); 實(shí)現(xiàn)的。h為我們傳入的 InvocationHandler 的實(shí)例,m3 為一個(gè)Method 對(duì)象,最終是通過(guò)調(diào)用 m3.invoke(target,args ) 實(shí)現(xiàn)被代理對(duì)象的方法調(diào)用。m3 是一個(gè) Method 類的實(shí)例。與此同時(shí),代理類 $Proxy0 除了實(shí)現(xiàn)所有接口類的方法外,還自動(dòng)幫我們添加了Object 類 的3個(gè)方法,分別是 toString()、hashcode()、equals()。并且在代理類的類初始化階段對(duì)這幾個(gè)Method 實(shí)例進(jìn)行賦值。

動(dòng)態(tài)代理類的名字規(guī)則

類全路徑名名 = pkgName + className
包名主要主要取決于接口是否是public 的。如果要代理的接口全都是public 的,則類的全路徑名是 com.sun.proxy,否則就是非public 接口的包名。
類名為 Proxy{n},n為當(dāng)前已經(jīng)創(chuàng)建的動(dòng)態(tài)代理類的個(gè)數(shù),由于有緩存的邏輯,只有不同數(shù)組接口的代理,才會(huì)創(chuàng)建新的類。n從0開(kāi)始,在同進(jìn)程內(nèi)依次遞增。源碼如下:

    Proxy$ProxyClassFactory#apply()                 
                        /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";// 默認(rèn)值 com.sun.proxy.
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num; //proxyClassNamePrefix 為“$Proxy”

個(gè)人理解

動(dòng)態(tài)代理是對(duì)接口的代理,而不是對(duì)類的代理。沒(méi)有接口則無(wú)從代理。

jvm由于不支持運(yùn)行時(shí)修改一個(gè)類或修改方法,如修改方法內(nèi)容,添加方法等。動(dòng)態(tài)代理由于是在運(yùn)行期間生產(chǎn)了新的代理類去擴(kuò)展功能,也算是對(duì)此種不足提供了一種支持。

動(dòng)態(tài)代理是 AOP 思想的一種實(shí)現(xiàn),完美的實(shí)現(xiàn)了一個(gè)切面。方便我們?cè)诿總€(gè)方法執(zhí)行前后定制功能。最常見(jiàn)的就是統(tǒng)計(jì)每個(gè)方法的執(zhí)行耗時(shí),在方法執(zhí)行前記錄時(shí)間戳,方法執(zhí)行完后再記錄一次。

靜態(tài)代理也可以做切面編程,只不過(guò)當(dāng)使用靜態(tài)代理時(shí),手動(dòng)編寫代理接口類的成本太大。相對(duì)于靜態(tài)代理,動(dòng)態(tài)代理是在此層面上做了一層抽象和封裝。定制化需求只需要在 InvocationHandler 的 invoke() 方法中添加就可以了。而且 invoke() 方法傳入的參數(shù)是 一個(gè) object,意味著任何類的實(shí)例都可以被代理(只要實(shí)現(xiàn)相應(yīng)接口),大大增強(qiáng)了InvocationHandler 子類復(fù)用性。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

很多 android 和 java 框架都應(yīng)用了動(dòng)態(tài)代理技術(shù)。如 Spring,retrofit等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java動(dòng)態(tài)代理機(jī)制的出現(xiàn),使得 Java 開(kāi)發(fā)人員不用手工編寫代理類,只要簡(jiǎn)單地指定一組接口及委托類對(duì)象,便能動(dòng)...
    廖111閱讀 234評(píng)論 0 1
  • JDK提供動(dòng)態(tài)代理(基于接口的),從表面上來(lái)看,主要需要一下兩個(gè)類: Proxy InvocationHandle...
    _palm閱讀 495評(píng)論 0 0
  • 關(guān)于Java中的動(dòng)態(tài)代理,我們首先需要了解的是一種常用的設(shè)計(jì)模式—代理模式,而對(duì)于代理,根據(jù)創(chuàng)建代理類的時(shí)間點(diǎn),又...
    編碼前線閱讀 1,761評(píng)論 0 4
  • 如果覺(jué)得再簡(jiǎn)述上閱讀代碼太困難可以點(diǎn)這里:Spring中動(dòng)態(tài)代理詳解 動(dòng)態(tài)代理在Java中有著廣泛的應(yīng)用,比如Sp...
    taylar_where閱讀 836評(píng)論 3 5
  • 推薦指數(shù): 6.0 書(shū)籍主旨關(guān)鍵詞:特權(quán)、焦點(diǎn)、注意力、語(yǔ)言聯(lián)想、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析,社會(huì)...
    Jenaral閱讀 5,989評(píng)論 0 5

友情鏈接更多精彩內(nèi)容