乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      靜態(tài)代理和動(dòng)態(tài)代理的理解

       漢無(wú)為 2018-08-26

      Java 靜態(tài)代理

      靜態(tài)代理通常用于對(duì)原有業(yè)務(wù)邏輯的擴(kuò)充。比如持有二方包的某個(gè)類,并調(diào)用了其中的某些方法。然后出于某種原因,比如記錄日志、打印方法執(zhí)行時(shí)間,但是又不好將這些邏輯寫(xiě)入二方包的方法里。所以可以創(chuàng)建一個(gè)代理類實(shí)現(xiàn)和二方方法相同的方法,通過(guò)讓代理類持有真實(shí)對(duì)象,然后在原代碼中調(diào)用代理類方法,來(lái)達(dá)到添加我們需要業(yè)務(wù)邏輯的目的。

      這其實(shí)也就是代理模式的一種實(shí)現(xiàn),通過(guò)對(duì)真實(shí)對(duì)象的封裝,來(lái)實(shí)現(xiàn)擴(kuò)展性。

      一個(gè)典型的代理模式通常有三個(gè)角色,這里稱之為**代理三要素**

      共同接口


      1. public interface Action {
      2. public void doSomething();
      3. }

      真實(shí)對(duì)象


      1. public class RealObject implements Action{
      2. public void doSomething() {
      3. System.out.println('do something');
      4. }
      5. }

      代理對(duì)象


      1. public class Proxy implements Action {
      2. private Action realObject;
      3. public Proxy(Action realObject) {
      4. this.realObject = realObject;
      5. }
      6. public void doSomething() {
      7. System.out.println('proxy do');
      8. realObject.doSomething();
      9. }
      10. }

      運(yùn)行代碼


      1. Proxy proxy = new Proxy(new RealObject());
      2. proxy.doSomething();

      靜態(tài)代理和動(dòng)態(tài)代理的理解

      這種代理模式也最為簡(jiǎn)單,就是通過(guò)proxy持有realObject的引用,并進(jìn)行一層封裝。

      靜態(tài)代理的優(yōu)點(diǎn)和缺點(diǎn)

      先看看代理模式的優(yōu)點(diǎn): 擴(kuò)展原功能,不侵入原代碼。

      再看看這種代理模式的缺點(diǎn):

      假如有這樣一個(gè)需求,有十個(gè)不同的RealObject,同時(shí)我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:


      1. realObject.doSomething();
      2. realObject1.doAnotherThing();
      3. realObject2.doTwoAnother();

      為了解決這個(gè)問(wèn)題,我們有方案一:

      為這些方法創(chuàng)建不同的代理類,代理后的代碼是這樣的:


      1. proxy.doSomething();
      2. proxy1.doAnotherThing();
      3. proxy2.doTwoAnother();

      當(dāng)然,也有方案二:

      通過(guò)創(chuàng)建一個(gè)proxy,持有不同的realObject,實(shí)現(xiàn)Action1、Action2、Action3接口,來(lái)讓代碼變成這樣:


      1. proxy.doSomething();
      2. proxy.doAnotherThing();
      3. proxy.doTwoAnother();

      于是你的代理模型會(huì)變成這樣:

      靜態(tài)代理和動(dòng)態(tài)代理的理解

      毫無(wú)疑問(wèn),僅僅為了擴(kuò)展同樣的功能,在方案一種,我們會(huì)重復(fù)創(chuàng)建多個(gè)邏輯相同,僅僅RealObject引用不同的Proxy。

      而在方案二中,會(huì)導(dǎo)致proxy的膨脹,而且這種膨脹往往是無(wú)意義的。此外,假如方法簽名是相同的,更需要在調(diào)用的時(shí)候引入額外的判斷邏輯。

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

      搞清楚靜態(tài)代理的缺點(diǎn)十分重要,因?yàn)閯?dòng)態(tài)代理的目的就是為了解決靜態(tài)代理的缺點(diǎn)。通過(guò)使用動(dòng)態(tài)代理,我們可以通過(guò)在運(yùn)行時(shí),動(dòng)態(tài)生成一個(gè)持有RealObject、并實(shí)現(xiàn)代理接口的Proxy,同時(shí)注入我們相同的擴(kuò)展邏輯。哪怕你要代理的RealObject是不同的對(duì)象,甚至代理不同的方法,都可以動(dòng)過(guò)動(dòng)態(tài)代理,來(lái)擴(kuò)展功能。

      簡(jiǎn)單理解,動(dòng)態(tài)代理就是我們上面提到的方案一,只不過(guò)這些proxy的創(chuàng)建都是自動(dòng)的并且是在運(yùn)行期生成的。

      動(dòng)態(tài)代理基本用法

      使用動(dòng)態(tài)代理,需要將要擴(kuò)展的功能寫(xiě)在一個(gè)InvocationHandler 實(shí)現(xiàn)類里:


      1. public class DynamicProxyHandler implements InvocationHandler {
      2. private Object realObject;
      3. public DynamicProxyHandler(Object realObject) {
      4. this.realObject = realObject;
      5. }
      6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      7. //代理擴(kuò)展邏輯
      8. System.out.println('proxy do');
      9. return method.invoke(realObject, args);
      10. }
      11. }

      這個(gè)Handler中的invoke方法中實(shí)現(xiàn)了代理類要擴(kuò)展的公共功能。

      到這里,需要先看一下這個(gè)handler的用法:


      1. public static void main(String[] args) {
      2. RealObject realObject = new RealObject();
      3. Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
      4. proxy.doSomething();
      5. }

      Proxy.newProxyInstance 傳入的是一個(gè)ClassLoader, 一個(gè)代理接口,和我們定義的handler,返回的是一個(gè)Proxy的實(shí)例。

      仔細(xì)體會(huì)這個(gè)過(guò)程,其實(shí)有點(diǎn)類似我們?cè)陟o態(tài)代理中提到的方案一,生成了一個(gè)包含我們擴(kuò)展功能,持有RealObject引用,實(shí)現(xiàn)Action接口的代理實(shí)例Proxy。只不過(guò)這個(gè)Proxy不是我們自己寫(xiě)的,而是java幫我們生成的,有沒(méi)有一點(diǎn)動(dòng)態(tài)的味道。

      讓我們?cè)倩仡櫼幌麓砣兀赫鎸?shí)對(duì)象:RealObject,代理接口:Action,代理實(shí)例:Proxy

      上面的代碼實(shí)含義也就是,輸入 RealObject、Action,返回一個(gè)Proxy。妥妥的代理模式。

      綜上,動(dòng)態(tài)生成+代理模式,也就是動(dòng)態(tài)代理。

      網(wǎng)上搜了不少文章,到了這里,接下來(lái)就是和cglib等動(dòng)態(tài)代理實(shí)現(xiàn)方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來(lái)做縱向挖掘。

      看一下源碼

      道理清楚了,但是這篇文章題目是搞懂,所以來(lái)看一下這個(gè)Proxy是如何自動(dòng)被生成的。入口就在newProxyInstance方法,核心代碼如下:


      1. private static final Class[] constructorParams =
      2. { InvocationHandler.class };
      3. public static Object newProxyInstance(ClassLoader loader,
      4. Class[] interfaces,
      5. InvocationHandler h)
      6. throws IllegalArgumentException
      7. {
      8. Class cl = getProxyClass0(loader, intfs);
      9. ...
      10. final Constructor cons = cl.getConstructor(constructorParams);
      11. if (!Modifier.isPublic(cl.getModifiers())) {
      12. AccessController.doPrivileged(new PrivilegedAction() {
      13. public Void run() {
      14. cons.setAccessible(true);
      15. return null;
      16. }
      17. });
      18. }
      19. return cons.newInstance(new Object[]{h});
      20. }

      整體流程就是:

      1、生成代理類Proxy的Class對(duì)象。

      2、如果Class作用域?yàn)樗接?,通過(guò) setAccessible 支持訪問(wèn)

      3、獲取Proxy Class構(gòu)造函數(shù),創(chuàng)建Proxy代理實(shí)例。

      生成Proxy的Class文件

      生成Class對(duì)象的方法中,先是通過(guò)傳進(jìn)來(lái)的ClassLoader參數(shù)和Class[] 數(shù)組作為組成鍵,維護(hù)了一個(gè)對(duì)于Proxy的Class對(duì)象的緩存。這樣需要相同Proxy的Class對(duì)象時(shí),只需要?jiǎng)?chuàng)建一次。

      第一次創(chuàng)建該Class文件時(shí),為了線程安全,方法進(jìn)行了大量的處理,最后會(huì)來(lái)到ProxyClassFactory的apply方法中,經(jīng)過(guò)以下流程:

      1、校驗(yàn)傳入的接口是否由傳入的ClassLoader加載的。

      2、校驗(yàn)傳入是否是接口的Class對(duì)象。

      3、校驗(yàn)是否傳入重復(fù)的接口。

      4、拼裝代理類包名和類名,生成.class 文件的字節(jié)碼。

      5、調(diào)用native方法,傳入字節(jié)碼,生成Class對(duì)象。


      1. proxyPkg = ReflectUtil.PROXY_PACKAGE + '.';
      2. long num = nextUniqueNumber.getAndIncrement();
      3. String proxyName = proxyPkg + proxyClassNamePrefix + num;
      4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
      5. proxyName, interfaces, accessFlags);
      6. return defineClass0(loader, proxyName,
      7. proxyClassFile, 0, proxyClassFile.length);

      看一下第四步生成.class文件字節(jié)碼的過(guò)程,主要分為兩個(gè)階段:


      1. addProxyMethod(hashCodeMethod, Object.class);
      2. addProxyMethod(equalsMethod, Object.class);
      3. addProxyMethod(toStringMethod, Object.class);
      4. for (int i = 0; i < interfaces.length;="" i++)="">
      5. Method[] methods = interfaces[i].getMethods();
      6. for (int j = 0; j < methods.length;="" j++)="">
      7. addProxyMethod(methods[j], interfaces[i]);
      8. }
      9. }
      10. methods.add(this.generateConstructor());
      11. for (List sigmethods : proxyMethods.values()) {
      12. for (ProxyMethod pm : sigmethods) {
      13. fields.add(new FieldInfo(pm.methodFieldName,
      14. 'Ljava/lang/reflect/Method;', ACC_PRIVATE | ACC_STATIC));
      15. methods.add(pm.generateMethod());
      16. }
      17. }
      18. methods.add(generateStaticInitializer());

      第一個(gè)階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理接口中的方法。再添加一下構(gòu)造方法以及靜態(tài)初始化方法。這要構(gòu)成了一個(gè)對(duì)象,存儲(chǔ)生成Proxy的Class的一些信息。

      到了這里,已經(jīng)把要構(gòu)造的Proxy的方法基本定義完成了,接下來(lái)就要生成這個(gè).class文件了。


      1. ByteArrayOutputStream bout = new ByteArrayOutputStream();
      2. DataOutputStream dout = new DataOutputStream(bout);
      3. dout.writeInt(0xCAFEBABE);
      4. ...
      5. dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
      6. ...
      7. return bout.toByteArray();

      看到這個(gè)CAFEBABE,就清楚第二階段的內(nèi)容了。CAFEBABE是Class文件的魔數(shù),關(guān)于Class文件這個(gè)咖啡寶貝的魔數(shù),相信做Java的人都知道。沒(méi)錯(cuò),第二階段就是生成字節(jié)碼。按JVM規(guī)范,寫(xiě)入Class文件中包括權(quán)限控制、方法表、字段表等內(nèi)容,生成符合規(guī)范的Class文件。最后返回對(duì)應(yīng)的字節(jié)碼。

      字節(jié)碼生成以后,通過(guò)調(diào)用native方法defineClass解析字節(jié)碼,就生成了Proxy的Class對(duì)象。

      Proxy構(gòu)造方法

      看一下Proxy的構(gòu)造方法字節(jié)碼生成部分:


      1. MethodInfo minfo = new MethodInfo('', '(Ljava/lang/reflect/InvocationHandler;)V',ACC_PUBLIC);
      2. DataOutputStream out = new DataOutputStream(minfo.code);
      3. code_aload(0, out);
      4. code_aload(1, out);
      5. out.writeByte(opc_invokespecial);
      6. out.writeShort(cp.getMethodRef(superclassName,'', '(Ljava/lang/reflect/InvocationHandler;)V'));
      7. ...

      關(guān)鍵在于,生成了一個(gè)參數(shù)為InvocationHandler的構(gòu)造方法,code加載的是jvm方法區(qū)中的代碼,然后通過(guò)invokespecial指令調(diào)用了父類構(gòu)造方法。

      查看生成的Class文件

      上面利用字節(jié)碼生成技術(shù)產(chǎn)生Class文件的過(guò)程,看起來(lái)可能比較晦澀,其實(shí)我們可以查看這個(gè)產(chǎn)生的Proxy到底是個(gè)什么樣子。

      注意ProxyGenerator中有這樣一個(gè)邏輯:


      1. if(saveGeneratedFiles) {
      2. ...
      3. FileOutputStream file = new FileOutputStream(dotToSlash(name) + '.class');
      4. file.write(classFile);
      5. ...
      6. }

      再看一下saveGeneratedFiles這個(gè)變量:


      1. private final static boolean saveGeneratedFiles =
      2. java.security.AccessController.doPrivileged(
      3. new GetBooleanAction('sun.misc.ProxyGenerator.saveGeneratedFiles'))
      4. .booleanValue();

      這是一個(gè)final類型的變量,通過(guò)GetBooleanAction方法讀取系統(tǒng)變量,獲取系統(tǒng)設(shè)置。默認(rèn)這個(gè)值是false,稍微看一下System這個(gè)類的源碼,發(fā)現(xiàn)有可以設(shè)置系統(tǒng)變量的Api,然后在程序的main 函數(shù)設(shè)置一下這個(gè)變量:

      System.getProperties().setProperty('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');

      這個(gè)時(shí)候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接雙擊利用 ide 反編譯。


      1. package com.sun.proxy;
      2. import java.lang.reflect.InvocationHandler;
      3. import java.lang.reflect.Method;
      4. import java.lang.reflect.Proxy;
      5. import java.lang.reflect.UndeclaredThrowableException;
      6. public final class $Proxy0 extends Proxy implements Action {
      7. private static Method m1;
      8. private static Method m3;
      9. private static Method m2;
      10. private static Method m0;
      11. public $Proxy0(InvocationHandler var1) throws {
      12. super(var1);
      13. }
      14. public final void doSomething() throws {
      15. try {
      16. super.h.invoke(this, m3, (Object[])null);
      17. } catch (RuntimeException | Error var2) {
      18. throw var2;
      19. } catch (Throwable var3) {
      20. throw new UndeclaredThrowableException(var3);
      21. }
      22. }
      23. ...
      24. static {
      25. try {
      26. ...
      27. m3 = Class.forName('Action').getMethod('doSomething', new Class[0]);
      28. } catch (NoSuchMethodException var2) {
      29. throw new NoSuchMethodError(var2.getMessage());
      30. } catch (ClassNotFoundException var3) {
      31. throw new NoClassDefFoundError(var3.getMessage());
      32. }
      33. }
      34. }

      省略一些無(wú)關(guān)代碼,可以看到兩個(gè)重要的方法。

      一個(gè)就是我們的代理方法doSomething、另一個(gè)就是構(gòu)造方法。

      這個(gè)$Proxy0 繼承 Proxy并調(diào)用了父類的構(gòu)造方法,回憶一下上文提到的invokeSpecial,怎么樣,對(duì)上了吧。

      看一下Proxy中這個(gè)構(gòu)造方法:


      1. protected Proxy(InvocationHandler h) {
      2. Objects.requireNonNull(h);
      3. this.h = h;
      4. }

      在看一下$Proxy0 的代理方法:

      super.h.invoke(this, m3, (Object[])null);

      再來(lái)回顧一下生成Proxy實(shí)例的過(guò)程:


      1. private static final Class[] constructorParams =
      2. { InvocationHandler.class };
      3. ...
      4. final Constructor cons = cl.getConstructor(constructorParams);
      5. ...
      6. return cons.newInstance(new Object[]{h});

      其實(shí)newInstance生成Proxy實(shí)例時(shí),通過(guò)$Proxy0的Class對(duì)象,選擇了這個(gè)InvocationHandler為參數(shù)的構(gòu)造方法,傳入我們定義的InvocationHandler并生成了一個(gè) Proxy0的實(shí)例!InvocationHandler 里有realObject的邏輯以及我們的擴(kuò)展邏輯,當(dāng)我們調(diào)用Proxy0的doSomething方法時(shí),就會(huì)調(diào)用到我們InvocationHandler 里 實(shí)現(xiàn)的invoke方法。

      對(duì)上面這個(gè)過(guò)程,做一張圖總結(jié)一下:

      靜態(tài)代理和動(dòng)態(tài)代理的理解

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多