這篇內容是上一篇[動態代理三部曲:上] - 動態代理是如何"坑掉了"我4500塊錢的補充,進一步分析篇。 建議兩者結合食用,醇香綿軟,入口即化。java
好了,不扯淡了,開始...面試
這裏爲啥是2開頭呢?由於上篇文章是1數組
這部份內容不知道各位小夥伴是怎麼感受的。最開始學習的時候,我是一頭霧水,不知道如何下手。當一步步結合反射、JVM內存模型,類加載機制後。再回過頭來就會發現一塊兒豁然開朗。編輯器
此篇內容的開始,讓咱們根據咱們demo中所用的類:RentHouseProcessorHandler來分析這個問題。
若是咱們用十六進制編輯器(好比:Sublime)打開這個RentHouseProcessorHandler.class文件: 工具
說實話這一行行的文字,最開始我是拒絕的。哦,上帝,爲何要讓我看這些鬼東西...其實若是咱們靜下心來,想當年高中時代學習數學,物理公式那樣去認真的對待它。就會發現它不過就是:一堆人爲賦予了特別含義的符號而已。學習
在咱們準備讀懂這些十六進制文字時,先讓咱們看一幅《Java虛擬機規範(Java SE 7)》對class文件的定義: spa
上圖的內容,其實很是的通俗易懂,不要由於是不常見的英文就抵觸它們。讓咱們嘗試着去翻譯它們: 一、魔數;二、次版本號;三、主版本號;四、常量池數量;五、常量池;六、權限標識;七、此類;八、父類;九、接口數目;十、接口;十一、變量數目;十二、變量;1三、方法數目;1四、方法....翻譯
實際上是不是發現了什麼,這不是就一個類應該存在的東西麼?沒錯啊,Class文件的結構就是固定了咱們編寫的Class類所存放的規則而已。最開始的我,覺得是深奧,沒敢去了解他們。當我躊躇滿志,鼓足勇氣去準備好好大幹一場的時候,才發現它太簡單了...就是一些規則,僅此而已。設計
雖然只是一些規則,但規則之中,總會有一些特別須要咱們去注意的地方:好比cp_info這個類型。在《深刻理解Java虛擬機》中,做者把以_info結尾的類型稱之爲「表」。這裏讓咱們也沿用這種表達方式。說白了,它就是擁有多級關係的類型。3d
cp_info 表示常量池(常量池:首先它和方法區中運行時常量池不是同一個內容。這裏的常量池存放了字面量和符號引用)。
符號引用:
這裏符號引用的做用,咱們想先一個問題。CPU執行程序的時候,其實是去尋找對應指令的內存地址。可是咱們的Class文件是先被編譯出來的,可是此時尚未被JVM加載到內存,因此確定是不可能存在內存地址這一說的。所以咱們的Class文件須要一些標識,讓JVM加載內容的時候從常量池中獲取到對應的符號引用,而後在映射到具體的內存地址上。
放到常量池的中數據項在《Java虛擬機規範(Java SE 7)》中一共有14個常量,每一種常量都是一個「表」,而且每種常量都用一個公共的tag來表示是哪一種類型的常量。具體內容以下圖:
這裏讓咱們先解讀一下這個常量池:讓咱們跳過u4的魔數、u2的此版本、u2的主版本。直接來看constant_pool_count。跳過對應的內容,那麼咱們的constant_pool_count就對應十六進制的20,對應十進制的32,也就是說常量池中有32個內容?實際不是的,由於設計者將第0個位置空出來另作打算。因此咱們的常量池只有31個內容。
咱們能夠經過javap命令證明這個問題。
接下來的一個字節:0a,翻譯成十進制就是10,對應咱們表中的CONSTANT_Methodref_info,而接下來的四個字節。分別表明索引3,20。這裏的索引表明什麼意思呢?注意理解下圖中標紅的地方:
接下來就不逐個解讀這些內容了,由於它就是一個對應的過程。若是小夥伴們有興趣能夠自行去嘗試解讀一番哦。推薦一個工具JavaClassViewer,能夠比較方便的查看這些內容:
常量池結束以後,即是咱們正常的變量,方法的信息。而這裏咱們須要瞭解一個全新的概念:描述符。 對於咱們來講一個變量、方法在java源碼裏是什麼樣子咱們很清楚。可是它們在class文件裏是什麼樣子的呢?這個樣子其實就被稱之爲:描述符。
上文談動態代理的時候,咱們瞭解到了ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);
方法中,經過:
dout.writeInt(0xCAFEBABE);
MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC);
複製代碼
等方法,構建了咱們的$Proxy0所須要的class結構,是否是和咱們javap出來的內容很相似?接下來讓咱們走進描述符。
通過上述內容的鋪墊,0xCAFEBABE是什麼意思,應該無需多言了。而<init>
是構造方法的意思。再加上(Ljava/lang/reflect/InvocationHandler;)V
以及ACC_PUBLIC就能夠表示爲InvocationHandler的public的構造方法,其中V表示無返回值。
<init>
:對象構造器方法。<clinit>
:類構造器方法。這裏的內容就被稱之爲方法的描述符,讓咱們簡單的看一些圖,加深這方面內容。
基本類型和void在描述符中都有一個大寫字符和他們對應; 那麼引用類型的描述符,又是什麼樣子的呢?
例以下圖中的:Ljava/lang/Object;就是表示這是一個Object類型。
而方法描述符的規則也很簡單,上圖中,總結出來就是一句話:
例如上圖中的:int[] m(int i,String s)轉換爲描述符:(ILjava/lang/String;)[I
不知道截了這麼多圖,你們對描述符有沒有比較明確的認識。說白了咱們咱們ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);
中所write的內容就是具體方法的描述符。
而後經過DataOutputStream轉成byte數組,那麼就是咱們Class文件所固定的內容了。所以,此時咱們的Class文件就已經構建完畢,接下來所須要的就是將其加載到內存中,供咱們使用。
到這準備結束class文件結構的內容。不知道小夥伴們是否有收穫。由於篇幅是在有限,有些內容又不是一句話倆句話能夠描述清楚的。因此有些內容一帶而過,實在抱歉。具體細節內容,你們能夠參考《深刻理解Java虛擬機》。
但願你們能夠諒解。