<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    我的家園

    我的家園

    上一篇中有提到spring aop的動態字節碼增強,我自己也沒看過spring 的實現方式,按照大家的說法應該是動態生產一個子類去重寫方法,由于自己沒去看過,暫且不表,接下去,可能還是打算從分析字節碼的角度去看類似于spring aop這個功能反應到字節碼有哪些變化,或者說實現方式,

    這個例子還是基于最簡單的HelloWorld,還請大家回顧一下前面的幾個章節,最要是這個 HelloWorld.class 文件的解讀?這個例子和前面兩個例子一下將基于它做一些變化,再去從字節碼的角度去比較看看究竟做了什么。

    ?

    首先考慮aop的一個最簡單應用場景,就是日志輸出,假設現在需要在輸出hello world 的前后都打印日志:代碼如下:

    ?

    ?

    public class HelloWorld{  
        public static void main(String [] arvgs){
        	System.out.println("before log");  
          System.out.println("hello world");
          System.out.println("after log");  
    }  
    }  

    ?

    ?

    編譯后的class 文件如下:

    ?

    ?

    00000000h: CA FE BA BE 00 00 00 32 00 21 0A 00 08 00 11 09 ; 漱壕...2.!......
    00000010h: 00 12 00 13 08 00 14 0A 00 15 00 16 08 00 17 08 ; ................
    00000020h: 00 18 07 00 19 07 00 1A 01 00 06 3C 69 6E 69 74 ; ...........<init
    00000030h: 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 ; >...()V...Code..
    00000040h: 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 ; .LineNumberTable
    00000050h: 01 00 04 6D 61 69 6E 01 00 16 28 5B 4C 6A 61 76 ; ...main...([Ljav
    00000060h: 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 ; a/lang/String;)V
    00000070h: 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0F ; ...SourceFile...
    00000080h: 48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61 76 61 0C ; HelloWorld.java.
    00000090h: 00 09 00 0A 07 00 1B 0C 00 1C 00 1D 01 00 0A 62 ; ...............b
    000000a0h: 65 66 6F 72 65 20 6C 6F 67 07 00 1E 0C 00 1F 00 ; efore log.......
    000000b0h: 20 01 00 0B 68 65 6C 6C 6F 20 77 6F 72 6C 64 01 ;  ...hello world.
    000000c0h: 00 09 61 66 74 65 72 20 6C 6F 67 01 00 0A 48 65 ; ..after log...He
    000000d0h: 6C 6C 6F 57 6F 72 6C 64 01 00 10 6A 61 76 61 2F ; lloWorld...java/
    000000e0h: 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 10 6A 61 ; lang/Object...ja
    000000f0h: 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 01 00 ; va/lang/System..
    00000100h: 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69 6F 2F ; .out...Ljava/io/
    00000110h: 50 72 69 6E 74 53 74 72 65 61 6D 3B 01 00 13 6A ; PrintStream;...j
    00000120h: 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 ; ava/io/PrintStre
    00000130h: 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00 15 28 ; am...println...(
    00000140h: 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E ; Ljava/lang/Strin
    00000150h: 67 3B 29 56 00 21 00 07 00 08 00 00 00 00 00 02 ; g;)V.!..........
    00000160h: 00 01 00 09 00 0A 00 01 00 0B 00 00 00 1D 00 01 ; ................
    00000170h: 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 ; ......*?.?....
    00000180h: 0C 00 00 00 06 00 01 00 00 00 01 00 09 00 0D 00 ; ................
    00000190h: 0E 00 01 00 0B 00 00 00 3D 00 02 00 01 00 00 00 ; ........=.......
    000001a0h: 19 B2 00 02 12 03 B6 00 04 B2 00 02 12 05 B6 00 ; .?...?.?...?
    000001b0h: 04 B2 00 02 12 06 B6 00 04 B1 00 00 00 01 00 0C ; .?...?.?.....
    000001c0h: 00 00 00 12 00 04 00 00 00 03 00 08 00 04 00 10 ; ................
    000001d0h: 00 05 00 18 00 06 00 01 00 0F 00 00 00 02 00 10 ; ................

    ?

    ?

    ?

    接下去首先將這個class文件和原始的HelloWorld的class 的文件對比,為了大家對比方面,把前面的class文件在拿過來:

    ?

    00000000h: CA FE BA BE 00 00 00 32 00 1D 0A 00 06 00 0F 09 ; 漱壕...2........
    00000010h: 00 10 00 11 08 00 12 0A 00 13 00 14 07 00 15 07 ; ................
    00000020h: 00 16 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 ; .....<init>...()
    00000030h: 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E ; V...Code...LineN
    00000040h: 75 6D 62 65 72 54 61 62 6C 65 01 00 04 6D 61 69 ; umberTable...mai
    00000050h: 6E 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 ; n...([Ljava/lang
    00000060h: 2F 53 74 72 69 6E 67 3B 29 56 01 00 0A 53 6F 75 ; /String;)V...Sou
    00000070h: 72 63 65 46 69 6C 65 01 00 0F 48 65 6C 6C 6F 57 ; rceFile...HelloW
    00000080h: 6F 72 6C 64 2E 6A 61 76 61 0C 00 07 00 08 07 00 ; orld.java.......
    00000090h: 17 0C 00 18 00 19 01 00 0B 68 65 6C 6C 6F 20 77 ; .........hello w
    000000a0h: 6F 72 6C 64 07 00 1A 0C 00 1B 00 1C 01 00 0A 48 ; orld...........H
    000000b0h: 65 6C 6C 6F 57 6F 72 6C 64 01 00 10 6A 61 76 61 ; elloWorld...java
    000000c0h: 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 10 6A ; /lang/Object...j
    000000d0h: 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D 01 ; ava/lang/System.
    000000e0h: 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69 6F ; ..out...Ljava/io
    000000f0h: 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 01 00 13 ; /PrintStream;...
    00000100h: 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 ; java/io/PrintStr
    00000110h: 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00 15 ; eam...println...
    00000120h: 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 ; (Ljava/lang/Stri
    00000130h: 6E 67 3B 29 56 00 21 00 05 00 06 00 00 00 00 00 ; ng;)V.!.........
    00000140h: 02 00 01 00 07 00 08 00 01 00 09 00 00 00 1D 00 ; ................
    00000150h: 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 ; .......*?.?...
    00000160h: 00 0A 00 00 00 06 00 01 00 00 00 01 00 09 00 0B ; ................
    00000170h: 00 0C 00 01 00 09 00 00 00 25 00 02 00 01 00 00 ; .........%......
    00000180h: 00 09 B2 00 02 12 03 B6 00 04 B1 00 00 00 01 00 ; ..?...?.?....
    00000190h: 0A 00 00 00 0A 00 02 00 00 00 03 00 08 00 04 00 ; ................
    000001a0h: 01 00 0D 00 00 00 02 00 0E                      ; .........
    ?

    ?

    這里不會對全部的class 文件進行說明,大家可以參考?HelloWorld.class 文件的解讀?和 helloWorld.class -方法解讀

    ?

    ? ?首先從常量池開始看,改動后的HelloWorld(下面稱?HelloWorld后),有32 個常量,而改動前的HelloWorld(下面稱HelloWorld前)只有28個常量,改動后多了4個常量,大家可以假設我們多輸出了兩個字符串,會多兩個常量,那么還有兩個呢?還記得constant_String 類型嗎?或者說有沒有主要到我們一般不會直接使用constant_UTF8類型的值,一般都有一個具體的類型來引用它,所以剩下的兩個便是constant_String 類型的引用,來引用我們要輸出的字符串,那么多了兩個常量對其他的常量會有什么影響呢,很容易想到,其他的常量的引用可能會增加1到4個索引

    ?

    ?好了,有了前面的理解,接下去在去仔細看看具體的常量:

    1、先看第一號常量:tag=0X 0A 為一個 constant_methodref 類型(對一個類中申明的方法的符號引用),根據它的定義,后面四個字節屬于它,class_index=0X00 08,name_and_type_index=0X00 11 ;和HelloWorld前相比會發現兩個index 都后移2;接下去2號常量一樣也只是所以后偏移了2位

    2、接下來看3、4、5、6號常量,對應到HelloWorld前應該是3、4號常量,先看HelloWorld前的意思,3號表示要輸出的字符串,4號表示println方法,在看HelloWorld后,3號表示一個輸出的字符串 “before log”,4號任然表示println方法,5號表示這個字符串“hello world”,6號表示最后一個 “after log”,由于HelloWorld后都是調用println方法,而對這個方法的描述只一次是可以理解的;到這里我們已經找出了2個多出的常量,

    3、接下去7、8號常量對應HelloWorld前的5、6號,可以發現也只是index向后偏移4位

    4、接下去的9到19號常量對應HelloWorld前的7到17號常量,發現如果是constant_UTF8類型則內容一樣,其他的也只是index 向后偏移2位或者4位

    5、接下去看20號到24號常量,對應HelloWorld前18、19、20 號,同樣先看HelloWorld前,18號constant_utf8類型是hello world字符串;19號時對printstream類的引用,20號時對println方法的定義;再看HelloWorld后20號時constant_utf8類型是before log 的字符串,20、22對應HelloWorld前的19、20;23號對應HelloWorld前的18號,24號表示constant_utf8類型的是after log 字符串;這里又有兩個多出來的字符串常量;這樣4個多出來的常量就全部找到了

    6、接下去大家可以發現剩下的常量,都是index可能有2位或者4位的偏移;

    ?

    7、接下我們關注?method_info結構,先說第一個method_info,表示的是init方法,我們的改動不會影響這個方法,所有跟前面一樣只是引用常量池的時候index有一些變化,這里不關注;

    ?

    8、接下去就是我們第三個重點了,第二個method_info結構,表示main方法;
    (1)、接下去的2個字節表示access_flags=0X 00 09,表示是一個ACC_PUBLIC和ACC_STATIC 的方法,
    (2)、在兩個字節0X 00 0D(
    HelloWorld前為0B,后移2位)表示 name_index,表示常量池中第13個常量為main?,即是?main?方法,

    (3)、在接下兩個字節 0X00 0E(HelloWorld前為0C,后移2位) 表示desciptor_index,表示常量池第14個常量為([Ljava/lang/Str?ing;)V,即是參數為String [],返回值為void;

    (4)、在接下去兩個字節0X 00 01 表示attribute_count,表示有1個attribute,索引接下去表示一個attribute_info 的結構;所有查看attribute_info 的結構定義

    ?

    ?

    attribute_info {          
     u2 attribute_name_index;  
     u4 attribute_length;  
     u1 info[attribute_length];  
     }  
    ?

    ?

    所以 在接下去的兩個字節 0X 00 0B(HelloWorld前為09,后移2位),查看第11好常量池為Code,然后code_attribute的定義:

    ?

    ?

    Code_attribute {  
             u2 attribute_name_index;  
             u4 attribute_length;  
             u2 max_stack;  
             u2 max_locals;  
             u4 code_length;  
             u1 code[code_length];  
             u2 exception_table_length;  
             {       u2 start_pc;  
                    u2 end_pc;  
                    u2 handler_pc;  
                    u2 catch_type;  
             }       exception_table[exception_table_length];  
             u2 attributes_count;  
             attribute_info attributes[attributes_count];  
        }

    ?

    ?

    ?在看這個結構體 attribute_name_index =0X 00 09,然后4個字節0X 00 00 00 3D(HelloWorld前為25,表示長度 為37個字節) 表示61個字節;這個61個字節是我們關注的;我們單獨拿出來

    ?

    HelloWorld后:

    00000199h: 00 02 00 01 00 00 00 19 B2 00 02 12 03 B6 00 04 ; ........?...?.
    000001a9h: B2 00 02 12 05 B6 00 04 B2 00 02 12 06 B6 00 04 ; ?...?.?...?.
    000001b9h: B1 00 00 00 01 00 0C 00 00 00 12 00 04 00 00 00 ; ?..............
    000001c9h: 03 00 08 00 04 00 10 00 05 00 18 00 06          ; .............

    ?

    ?對 helloword前的解析參見這里?中的第二塊

    ?

    (1)、0X 00 02 表示max_stack;表示該方法執行的時候操作數棧最大的長度;這里表示操作數棧的長度為2;

    (2)、0X 00 01 表示?max_locals;表示方法局部變量所需要的空間的長度

    (3)、0X 00 00 00 19 表示code_length=25;即后面的25個字節為code的內容;

    (4)、 B2 00 02 12 03 B6 00 04?B2 00 02 12 05 B6 00 04 B2 00 02 12 06 B6 00 04 B1 :25個字節表示的便是code 的內容;

    ? ? ? 該code[] 包含的實現該方法的JVM 的實際的字節,

    ?

    • 0X B2 :getstatic 指令:表示獲取指定類的靜態域,并將其值壓入棧頂,后面的0X 00 02 ;查看2號常量池,即將out(sysytem.out)壓入棧
    • 0X 12 :ldc:表示將一個常量池壓入操作棧,后面的0X 03 便是這個操作數,查看第3號常量池,為berfore log,我們要輸出的內容;
    • 0X B6 : invokevirtual,調用實例方法,后面的0X 00 04 ,查看4號常量池 表示java/io/PrintStream的println方法,這個指令彈出兩個操作數,即是調用 out.print("before log");
    • 0X B2 :getstatic 指令:表示獲取指定類的靜態域,并將其值壓入棧頂,后面的0X 00 02 ;查看2號常量池,即將out(sysytem.out)壓入棧
    • 0X 12 :ldc:表示將一個常量池壓入操作棧,后面的0X 05 便是這個操作數,查看第5號常量池,為hello world,我們要輸出的內容;
    • 0X B6 : invokevirtual,調用實例方法,后面的0X 00 04 ,查看4號常量池 表示java/io/PrintStream的println方法,這個指令彈出兩個操作數,即是調用 out.print("hello world");
    • 0X B2 :getstatic 指令:表示獲取指定類的靜態域,并將其值壓入棧頂,后面的0X 00 02 ;查看2號常量池,即將out(sysytem.out)壓入棧
    • X 12 :ldc:表示將一個常量池壓入操作棧,后面的0X 06 便是這個操作數,查看第3號常量池,為after log,我們要輸出的內容;
    • X B6 : invokevirtual,調用實例方法,后面的0X 00 04 ,查看4號常量池 表示java/io/PrintStream的println方法,這個指令彈出兩個操作數,即是調用 out.print("after log");
    • 0X?B1 : return ;返回void

    (5)、0X 00 00 :表示exception_table_length=0;也就是說沒有異常處理;

    (6)、0X 00 01 :表示attributes_count=1;接下來有一個attribute_info 的結構:

    ? ?1)、0X 00 0C (HelloWorld前為0A,后移2位) :表示?attribute_name_index,查看10號常量池,為LineNumberTable ;

    查 看LineNumberTable ?屬性的定義:

    ?

    ? ?2)、0X 00 00 00 12 (HelloWorld前為0A) :表示attribute_length=18,

    ? ?3)、0X 00 04 :表示line_number_table_length=4,即后面有4個line_number_info 結構

    ? ? ? ?3.1)、0X 00 00 表示 start_pc;新行開始時,代碼數組的偏移量,該偏移量從代碼數組的起始位置開始;

    ? ? ? ?3.2)、0X 00 03 表示 line_number=3

    ?

    ? ? ? ?3.3)、0X 00 08 表示 start_pc;新行開始時,代碼數組的偏移量,該偏移量從代碼數組的起始位置開始;

    ? ? ? ?3.4)、0X 00 04 表示 line_number=4

    ?

    ? ? ? ?3.5)、0X 00 10表示 start_pc;新行開始時,代碼數組的偏移量,該偏移量從代碼數組的起始位置開始;

    ? ? ? ?3.6)、0X 00 05 表示 line_number=5

    ?

    ? ? ? ?3.7)、0X 00 18 表示 start_pc;新行開始時,代碼數組的偏移量,該偏移量從代碼數組的起始位置開始;

    ? ? ? ?3.8)、0X 00 06 表示 line_number=6

    ?

    LineNumberTable ?中包含了一些調試信息,不做討論;

    ?

    這樣main方法就好了;

    ?

    9、接下去表示SourceFile屬性,不去關注;

    ?

    10、終結一下我們比較的結構,可以發現首先是在常量池中會增加4個常量,這是由于我們多輸出了兩個字符串引發的,接著由于這4個常量的出現,打亂了原來常量池的順序,導致索引大量向后偏移;最后就是main方法的coed 的字節碼增加了,由原來的9個增加的 25個;再仔細看原來這個9個字節,其實前8個是方法體,最后一個是return;所以當我們增加了2個輸出語句,這樣3*8=24 再加1個返回就是25個字節了

    ?

    最后,我們考慮如果我們需要用這種字節碼增強的方式去實現aop的話,那么最大的麻煩在于需要后移原來的常量池的索引,如果能夠保持原來的常量池中的常量的位置,新增的常量只是加在最后面的話,這樣就可以省去大量的工作,下一篇我們將嘗試用這種方法去直接修改二進制碼來嘗試;在下去希望可以通過程序實現我們手工做的事情;






    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 免费一区二区无码东京热| 深夜A级毛片视频免费| 三年片在线观看免费观看大全一 | 国产精品综合专区中文字幕免费播放| 大学生一级毛片免费看| 亚洲情A成黄在线观看动漫软件| 两性刺激生活片免费视频 | 亚洲1区2区3区精华液| 免费观看的av毛片的网站| 亚洲日韩av无码中文| 免费无码又爽又刺激高潮 | 免费h黄肉动漫在线观看| 国产精品亚洲а∨无码播放麻豆| 国产免费131美女视频| 乱淫片免费影院观看| 亚洲无线码一区二区三区| 久草免费福利视频| 亚洲视屏在线观看| 免费无码黄网站在线观看| a毛片成人免费全部播放| 亚洲国产成人久久综合碰碰动漫3d| 最近中文字幕国语免费完整 | 亚洲精品高清一二区久久| 男女拍拍拍免费视频网站| 久久亚洲成a人片| 9久9久女女免费精品视频在线观看| 亚洲精品中文字幕无码A片老| 大胆亚洲人体视频| 国产精品99久久免费观看| 亚洲av成人一区二区三区| 免费国产真实迷j在线观看| 13小箩利洗澡无码视频网站免费| 亚洲日产2021三区| 亚洲国产精品自产在线播放| 成全动漫视频在线观看免费高清版下载| 亚洲欧洲高清有无| 四虎永久成人免费| 8x网站免费入口在线观看| 色五月五月丁香亚洲综合网| 亚洲第一AV网站| 国产精品免费小视频|