[動態代理三部曲:中] - 從動態代理,看Class文件結構定義

前言

這篇內容是上一篇[動態代理三部曲:上] - 動態代理是如何"坑掉了"我4500塊錢的補充,進一步分析篇。 建議兩者結合食用,醇香綿軟,入口即化。java

好了,不扯淡了,開始...面試

正文

二、Class 文件的格式

這裏爲啥是2開頭呢?由於上篇文章是1數組

這部份內容不知道各位小夥伴是怎麼感受的。最開始學習的時候,我是一頭霧水,不知道如何下手。當一步步結合反射、JVM內存模型,類加載機制後。再回過頭來就會發現一塊兒豁然開朗。編輯器

此篇內容的開始,讓咱們根據咱們demo中所用的類:RentHouseProcessorHandler來分析這個問題。
若是咱們用十六進制編輯器(好比:Sublime)打開這個RentHouseProcessorHandler.class文件: 工具

十六進制的Class文件

說實話這一行行的文字,最開始我是拒絕的。哦,上帝,爲何要讓我看這些鬼東西...其實若是咱們靜下心來,想當年高中時代學習數學,物理公式那樣去認真的對待它。就會發現它不過就是:一堆人爲賦予了特別含義的符號而已。學習

在咱們準備讀懂這些十六進制文字時,先讓咱們看一幅《Java虛擬機規範(Java SE 7)》對class文件的定義: spa

《Java虛擬機規範(Java SE 7)》


2.一、Class文件的規範結構

2.1.一、標準結構

上圖的內容,其實很是的通俗易懂,不要由於是不常見的英文就抵觸它們。讓咱們嘗試着去翻譯它們: 一、魔數;二、次版本號;三、主版本號;四、常量池數量;五、常量池;六、權限標識;七、此類;八、父類;九、接口數目;十、接口;十一、變量數目;十二、變量;1三、方法數目;1四、方法....翻譯

Class結構

實際上是不是發現了什麼,這不是就一個類應該存在的東西麼?沒錯啊,Class文件的結構就是固定了咱們編寫的Class類所存放的規則而已。最開始的我,覺得是深奧,沒敢去了解他們。當我躊躇滿志,鼓足勇氣去準備好好大幹一場的時候,才發現它太簡單了...就是一些規則,僅此而已。設計

2.1.二、特別注意的結構:表

雖然只是一些規則,但規則之中,總會有一些特別須要咱們去注意的地方:好比cp_info這個類型。在《深刻理解Java虛擬機》中,做者把以_info結尾的類型稱之爲「表」。這裏讓咱們也沿用這種表達方式。說白了,它就是擁有多級關係的類型。3d

cp_info 表示常量池(常量池:首先它和方法區中運行時常量池不是同一個內容。這裏的常量池存放了字面量和符號引用)。


符號引用:

符號引用:

  • 類和接口的全侷限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

這裏符號引用的做用,咱們想先一個問題。CPU執行程序的時候,其實是去尋找對應指令的內存地址。可是咱們的Class文件是先被編譯出來的,可是此時尚未被JVM加載到內存,因此確定是不可能存在內存地址這一說的。所以咱們的Class文件須要一些標識,讓JVM加載內容的時候從常量池中獲取到對應的符號引用,而後在映射到具體的內存地址上。


放到常量池的中數據項在《Java虛擬機規範(Java SE 7)》中一共有14個常量,每一種常量都是一個「表」,而且每種常量都用一個公共的tag來表示是哪一種類型的常量。具體內容以下圖:

表類型.png

這裏讓咱們先解讀一下這個常量池:讓咱們跳過u4的魔數、u2的此版本、u2的主版本。直接來看constant_pool_count。跳過對應的內容,那麼咱們的constant_pool_count就對應十六進制的20,對應十進制的32,也就是說常量池中有32個內容?實際不是的,由於設計者將第0個位置空出來另作打算。因此咱們的常量池只有31個內容。

constant_pool_count

咱們能夠經過javap命令證明這個問題。

javap

接下來的一個字節:0a,翻譯成十進制就是10,對應咱們表中的CONSTANT_Methodref_info,而接下來的四個字節。分別表明索引3,20。這裏的索引表明什麼意思呢?注意理解下圖中標紅的地方:

常量池只有31個內容

接下來就不逐個解讀這些內容了,由於它就是一個對應的過程。若是小夥伴們有興趣能夠自行去嘗試解讀一番哦。推薦一個工具JavaClassViewer,能夠比較方便的查看這些內容:

JavaClassViewer-左視圖

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出來的內容很相似?接下來讓咱們走進描述符。

2.二、變量、方法的描述符

通過上述內容的鋪墊,0xCAFEBABE是什麼意思,應該無需多言了。而<init>是構造方法的意思。再加上(Ljava/lang/reflect/InvocationHandler;)V以及ACC_PUBLIC就能夠表示爲InvocationHandler的public的構造方法,其中V表示無返回值。

  • <init>:對象構造器方法。
  • <clinit>:類構造器方法。

這裏的內容就被稱之爲方法的描述符,讓咱們簡單的看一些圖,加深這方面內容。

類型描述符

基本類型和void在描述符中都有一個大寫字符和他們對應; 那麼引用類型的描述符,又是什麼樣子的呢?

「L」 + 類型的全限定名 + 「;」

例以下圖中的:Ljava/lang/Object;就是表示這是一個Object類型。

方法對應的描述符

而方法描述符的規則也很簡單,上圖中,總結出來就是一句話:

(參數類型1參數類型2參數類型3 ...)返回值類型

例如上圖中的:int[] m(int i,String s)轉換爲描述符:(ILjava/lang/String;)[I

不知道截了這麼多圖,你們對描述符有沒有比較明確的認識。說白了咱們咱們ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);中所write的內容就是具體方法的描述符。

而後經過DataOutputStream轉成byte數組,那麼就是咱們Class文件所固定的內容了。所以,此時咱們的Class文件就已經構建完畢,接下來所須要的就是將其加載到內存中,供咱們使用。

2.三、收尾

到這準備結束class文件結構的內容。不知道小夥伴們是否有收穫。由於篇幅是在有限,有些內容又不是一句話倆句話能夠描述清楚的。因此有些內容一帶而過,實在抱歉。具體細節內容,你們能夠參考《深刻理解Java虛擬機》。

但願你們能夠諒解。

我是一個應屆生,最近和朋友們維護了一個公衆號,內容是咱們在從應屆生過渡到開發這一路所踩過的坑,已經咱們一步步學習的記錄,若是感興趣的朋友能夠關注一下,一同加油~

我的公衆號:IT面試填坑小分隊
相關文章
相關標籤/搜索