天天看點

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

    由之前動态代理的學習再次接觸到反射這個知識點,第二次接觸了是以做了一些稍微深入的了解。那麼,對于反射這部分的内容我打算分三篇部落格來總結。本篇部落格先對反射做一個大概的了解,包括反射有關的rtti、定義的了解以及涉及到的其他知識的簡介。

    java之前我接觸反射這個知識,是在大話設計中的抽象工廠模式裡,通過反射+配置檔案來優化抽象工廠提高其應對需求變更的靈活性。當時對于反射的認知僅僅是它是一種技術,一種執行個體化對象的技術,一種執行個體化對象不依賴于寫死的代碼的技術。簡單的說就是,它是一種可以擺脫用new去執行個體化對象,顯然它應付與對象變換的能力是強大的。

    其實,反射不論在什麼語言裡都是一樣的,隻不過實作的手段不一樣而已。之前對代理模式的深入學習過程中又遇到了反射,是以決定這次要一探究竟。

    java可以在運作時加載、探知、使用編譯期間完全未知的class。再簡單一點說就是java可以在運作時獲得任一一個類的資訊、建構類的class對象(強調:不是該類的對象,是後面提高的類類型)、生成類執行個體、調用methods。這裡類的資訊包括它的基類、所實作的接口、方法等。

    個人覺得這裡比較難了解的是“編譯期間完全未知”。是以,特别解釋一下。如下的代碼。首先,這個note類是不存在的,也就是說這段代碼有錯。分别執行兩個方法就可以看出分别了,其實方法2編譯時就無法通過,提示類是不能識别的類型,因為本來就不存在這個類。而方法1則時可以通過編譯,執行時能列印“方法執行中”。但是執行個體化時會報空指針的錯誤。這兩種不同時刻産生的錯誤就說明了“編譯期間完全未知”這個說法。

[java]

view plaincopyprint?

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

public class client {  

     public static void main(string[] args)   

             throws instantiationexception, illegalaccessexception, classnotfoundexception{   

                       system.out.println("方法執行中!"); //列印說明進入該方法

                       //方法1:通過反射執行個體化類note   

        class c=class.forname("com.zjjreflect.note");  

           object o=c.newinstance();      

           //方法2:直接執行個體化類note   

           //object o=new note();

         }  

}  

     rtti是run-time  type information 的縮寫,意思是運作時類型資訊。,rtti提供了運作時确定對象類型的方法。但是,rtti并不是一種技術,應該是一種概念。因為不同的語言對rtti的實作形式是不一樣的。簡單的說就是在程式運作時去獲得一個對象所對應的類的資訊。這麼說有點模糊,還是結合rtti在某種語言裡的實作來了解好一些。java 中就是主要有:類型轉換、類類型class、instanceof展現了rtti。

    class是所有類和接口的抽象,比如類的名字、類實作的接口、方法、屬性等等。你可以通過某個對象的class對象來擷取類的資訊。這裡不做詳細介紹,後面有單獨的部落格補充。

    類加載的過程就是将.class加載到記憶體中。這裡為什麼要提到classloader呢?因為反射的過程使用到了classloader,并且不同的類需要相對應的classloader來加載。也就是說classloader是和類是配對工作的,然後反射的特點卻是在運作時才會知道類的資訊

    是以我們也要對這部分的内容作個了解。也是為了能更好、更深刻的對反射了解。java的classloader有四種分别為:

         bootstrap classloader :引導(也稱為原始)類加載器。                 

         extension classloader :擴充類加載器。

         application classloader:應用程式類加載器。

         user defined classloader:自定義類加載器。

    總結:在程式運作時通過類類型class獲得目标類的資訊,然後在類資訊的基礎上使用相對應的類加載器加載到記憶體中,再然後對這個類中進行執行個體化,方法調用等的使用的整個過程。就是反射的詳細的說法!!

上面簡要的提了一下java反射機制中涉及到的一些相關知識,那麼classloader就是其中之一。緊接着就詳細的對classloader做一個相對深入的了解。作為了解需要知道的是,其實類類型(class class)是和classloader分不開的,因為classloader需要的資訊是由它提供的。類類型将在下一篇部落格介紹。

    classloader是負責加載類的對象,作用是根據jvm請求提供的類資訊,将請求的類加載的記憶體中或者說加載到jvm中。另外,每一個類的class對象(注意class是類類型)都持有一個對應的classloader的引用。可以通過class對象的getclassloader()方法得到。類和它的classloader是對應的,是以類隻能通過它對應的classloader加載。

    注意:數組類的 class 對象不是由類加載器建立的,而是由 java 運作時根據需要自動建立。數組類的類加載器由 class.getclassloader() 傳回,該加載器與其元素類型的類加載器是相同的;如果該元素類型是基本類型,則該數組類沒有類加載器。

    jvm在運作時會産生三個classloader,bootstrap classloader、extension classloader和app classloader。

    bootstrap classloader:是用c++編寫的,是jvm的内置加載器,它的名字是null。它用來加載核心類庫,即在lib下的類庫。做個實驗,首先,string類肯定是java的核心類,那我們就以它為例來看看:    

public static void main(string[] args){  

        string a="x";  

        system.out.println(a.getclass().getclassloader());  

    }  

           我們通過代碼來獲得string加載對應的classloader的名字輸出的結果為null。

    extension classloader:加載lib/ext下的類庫。

    app classloader:加載classpath裡的類庫。

    之前我們說過,每一個class對象都會持有一個對應的classloader的引用。每一個classloader對象也會持有一個parent classloader的引用。這裡需要特别注意的是:這裡所指的的parent classloader不是我們熟悉的繼承關系,不是父類!!首先,我們要知道這裡說的是classloader對象,也就是說這的parent

classloader其實是一個對象的引用。下面看一張圖,了解一下classloader對象之間的層次關系:

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

      我們這裡可以在做一個實驗,來體會一下這個層次關系。代碼如下:

public static void main(string[] args){       

        classloader c =testclassloader.class.getclassloader();  

        do {  

            system.out.println(c.getclass().getname());  

            c=c.getparent();  

        }while(c!=null);          

輸出的結果為:

sun.misc.launcher$appclassloader

sun.misc.launcher$extclassloader

    層次關系中我們了解到了很重要的一點:加載器對象之間的引用關系。被引用的對象稱之為引用對象的父加載器,可以通過getparent()方法得到。那麼雙親加載機制就是基于這種引用的層次關系。即:當一個classloader接到請求時,它不是直接加載對應的類,而是詢問它引用的classloader是否能夠加載,而這個父classloader則會詢問自己的引用的classloader是否加載了該類。隻有當所有的父classloader都沒有加載該類時,這個最初的classloader才自己去加載申請的類。

    很繞啊,文字說不清楚還是上圖吧!

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

    雙親加載機制可以一定程度上保證安全性,因為隻要頂層classloader能加載的東西就一定不會讓下層的classloader有機會加載。也就保證了有些自定義的帶有破壞性的類不會被加載到jvm核心中。

    結語:classloader相對難了解的地方一個是,對象将的層次關系和父加載器。另一個是雙親加載機制。這裡提供一個視訊供大家參考。  

    為了差別于我們常用的class,是以給了個中文名類類型。目的就是為了知道此class非彼class。前面已經介紹了class loader,它的作用是根據提供的資訊來加載類到記憶體中。我之前有提過這個類資訊的提供者就是本篇部落格要介紹的class。提醒:一個類的class對象和它的instance是不一樣的,切記,不然你會混亂的。開始了!

    class類是所有類(注意是對象)的共有資訊的抽象,比如該類實作的接口、對應的加載器、類名等等。一句話,類類型儲存了每個類所對應的類型資訊。每一個類都有一個class對象,這個對象在類被加載後由jvm自動構造。也是由jvm管理的,class類是沒有公共的構造方法的。

    class對象對于類來說就像是,dna對于每個人,裡面有你的一切生物資訊。java中可以通過class來取得類的執行個體,也許将來的将來通過你的dna也能得到你的另一個執行個體。科幻電影裡是已經實作了。ok,概念應該有個初步的認識了。

    方法的介紹本來不應該這麼簡單,但是發現一句兩句的說不清楚,并且對于java的了解有很好的幫助。是以臨時決定這部分單獨的寫一篇部落格。這裡就簡單的列幾個,之前用過的方法。

forname:傳回與帶有給定字元串名的類或接口相關聯的 class 對象。

getname():一個class對象描述了一個特定類的屬性,class類中最常用的方法getname以 string 的形式傳回此 class 對象所表示的實體(類、接口、數組類、基本類型或 void)名稱。

newinstance():建立class對象描述的類型的新執行個體。newinstance()方法調用預設構造器(無參數構造器)初始化建立對象。

getclassloader():傳回該類的類加載器。

getinterfaces():确定此對象所表示的類或接口實作的接口。

getcomponenttype():傳回表示數組元件類型的 class。

getsuperclass():傳回表示此 class 所表示的實體(類、接口、基本類型或 void)的超類的 class對象

isarray():判定此 class 對象是否表示一個數組類。

    獲得class對象的方法有三種        

    (1)利用object.getclass()方法擷取該對象的class執行個體;

    (2)使用class.forname()靜态方法,用類的名字擷取一個class執行個體

    (3)運用類的.class的方式來擷取class執行個體,對于基本資料類型的封裝類,還可以采用.type來擷取相對應的基本資料類型的class執行個體

    這裡需要注意的是虛拟機隻會産生一份位元組碼, 用這份位元組碼可以産生多個執行個體對象。也就是說class對象隻會有一個。看如下代碼:

    測試類

public class test {  

   static {  

       system.out.println("靜态初始化");  

   }  

   {  

       system.out.println("非靜态初始化");  

    用戶端

    public static void main(string[] arg) throws classnotfoundexception, instantiationexception, illegalaccessexception{  

               //方法1   

       class c=class.forname("com.zjj.classtest.test");  

               //方法2   

        c=test.class;  

               //方法3   

        test t=new test();  

        c=t.getclass();  

               test t2=new test();  

輸出結果為:

             靜态初始化

      非靜态初始化

    大家知道靜态初始化方法是在類加載的時候執行的,非靜态初始化方法是在類被執行個體化的時候執行的。而輸出結果隻列印了一次“靜态初始化”,這就說明三次得到的class對象都是同一個。

    也就是說,在運作期間,如果我們要産生某個類的對象或者的得到某個類的class對象,java虛拟機(jvm)會檢查該類型的class對象是否已被加載。如果沒有被加載,jvm會根據類的名稱找到.class檔案并加載它。一旦某個類型的class對象已被加載到記憶體,就可以用它來産生該類型的所有對象

    本篇總結:至此,應該可以了解了class也是一個類,隻不過它是所有類的一個抽象,名字又和我們所知道的class一樣容易造成混淆。總的來說,每一個類都有對應的一個class對象來儲存這個類的資訊,這個class對象由jvm構造和管理。class對象的存在是java反射的基礎。

    反射機制總結:反射機制是java的一個重要的内容,為java提供了運作時加載類的能力,也就是動态性。class是資訊提供者,class loader是加載工具,二者都是反射機制最基礎的部分。那麼所謂的反射就是解除耦合,方式就是通過class取得未知類的資訊,而後執行個體化。當然class loader的所做的工作是隐藏的,是class對象去調用的。是以無需顯示的自己調用。

    反射機制這幾篇部落格寫下來發現涉及到java類的加載機制,這部分的内容也比較獨立的一部分,是以單另一篇來寫。在java中任何的類都是需要加載到jvm中才能運作的。之前class loader介紹了類的加載機制,那麼這裡要說的是不同加載方式之間的對比,好能對java類的執行個體化過程有更深刻的體會。  

    我們說代碼裡出現new關鍵字意味着對于可能變動的代碼,耦合過高了。遇到這種情況我們會用反射機制來去除new關鍵字,這在代理模式裡我們見過了。實際上也就是用了class.newinstance來代替。這說明這兩種方式都可以得到相同的對象執行個體,但是它們之間存在差別,耦合度不同。

    實際上在了解上我們可以認為,class.newinstanc方式來執行個體化對象是對new關鍵字的拆分成兩步了。因為,class.newinstance的使用是有前提的,要保證類已經加載到jvm中,并且已經連結。看如下代碼:

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

<span style="font-family:fangsong_gb2312;font-size:18px;"><span style="font-family:fangsong_gb2312;">public static void main(string[] arg) throws classnotfoundexception, instantiationexception, illegalaccessexception{  

        //從目前線程取得正在運作的加載器   

        classloader cl=thread.currentthread().getcontextclassloader();  

        cl.loadclass("com.zjj.classtest.test");    //加載測試類到jvm

        class c2=cl.getclass();         //得到類的class對象

        c2.newinstance();               //執行個體化對象     

}</span></span>  

    這裡不用class.forname來得到class對象是為了保證類被加載了但是沒有被連結。 這段代碼看着貌似沒什麼錯,編譯也沒有問題,但是運作的時候就出錯了。也就是說通過如上方法加載的類是沒有被連結的,是以newinstance方法無法執行。

    前面說了解上可以簡單的認為是通過class.instance方式是new拆分的兩步,但是事實上new要比class.instance做的多。class.instance方法隻能通路無參數的構造函數,new則都可以通路。建立一個有兩個構造函數的測試類,看用戶端調用代碼:

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

        class c=class.forname("com.zjj.classtest.test");  

       c.newinstance();           

        new test("ni");       

    輸出結果為:

        無參數的構造函數

        帶參數的構造函數

    如果在newinstance中傳入參數去調用帶參數的構造函數的話是會報錯的,無法通過編譯。相對來說newinstance是弱類型,new是強類型。

    講這兩個的差別之前我們先要了解,jvm會執行靜态代碼段,要記住一個概念,靜态代碼是和class綁定的,class裝載成功就表示執行了靜态代碼了,以後也就不會再走這段靜态代碼了。 也就是說靜态代碼段是隻會執行一次的,在類被加載的時候。另外我們還需要知道,類的加載過程分為裝載、連接配接、初始化。還有就是,jvm遇到類請求時它會先檢查記憶體中是否存在,如果不存在則去加載,存在則傳回已存在的class對象。

    那麼這兩個方法的差別就在于執行的這三個過程不一樣。forname有兩個函數(多态),三個參數時forname(string classname, boolean initialize, classloader loader)第二個參數為true時則類會連結,會初始化。為false時,如果原來不存在則一定不會連接配接和初始化,如果原來存在被連接配接的class對象,則傳回該對象但是依然不會初始化。單參數時,預設initialize是為true的。

    loadclass也是多态loadclass(string name)單參數時, resolve=false。如果該類已經被該類裝載器所裝載,那麼,傳回這個已經被裝載的類型的class的執行個體,否則,就用這個自定義的類裝載器來裝載這個class,這時不知道是否被連接配接。絕對不會被初始化!這時唯一可以保證的是,這個類被裝載了。但是不知道這個類是不是被連接配接和初始化了。

    loadclass(string name, boolean resolve)resolve=true時,則保證已經裝載,而且已經連接配接了。 resolve=falses時,則僅僅是去裝載這個類,不關心是否連接配接了,是以此時可能被連接配接了,也可能沒有被連接配接。下面通過測試來驗證以上說的内容,代碼如下:

    test類:

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

<span style="font-family:fangsong_gb2312;font-size:18px;"><span style="font-family:fangsong_gb2312;">public class test {  

   }     

   public test(){  

       system.out.println("無參數的構造函數");  

   public test(string str){  

       system.out.println("帶參數的構造函數");  

    測試一:用戶端調用代碼

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

     }  

    輸出結果為:靜态初始化

    說明:class.forname時類執行了裝載、連接配接、初始化三個步驟。

    測試二:用戶端代碼改為

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

        class c=class.forname("com.zjj.classtest.test", false, cl);  

    輸出結果為:initialize=true時輸出,靜态初始化。為false時沒有輸出

    說明:為true時類執行了裝載、連接配接、初始化三個步驟。為false時沒有初始化,為知是不是連接配接。

    測試三:用戶端代碼改為

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

               c.newinstance();  

        靜态初始化

        非靜态初始化

    說明:為了保證jvm中不存在之前加載過的類,特地清理了jvm記憶體。但是輸出結果不變,說明為false時執行了裝載和連結,否則newinstance是無法執行的(前面說過了newinstance的執行條件)。但是資料說可能還存在不連接配接的情況!!有待考證。

    測試四:用戶端代碼改為

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

               class c=class.forname("com.zjj.classtest.test");  

        class c=class.forname("com.zjj.classtest.test", true, cl);  

             }  

    說明:如果原來存在加載過的類,那麼第二次執行加載請求時傳回存在的。因為,靜态初始化隻執行了一次。

    測試五:用戶端代碼改為

java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass
java反射機制剖析(一)—簡介 回顧 定義 RTTI 類類型java.lang.Class Class loader 簡介 分類 層次關系 雙親加載機制 概念 常用方法  怎麼得到 結語 new和Class.newInstance Class.forName和classLoad.loadClass

<span style="font-family:fangsong_gb2312;font-size:18px;"><span style="font-family:fangsong_gb2312;">public static void main(string[] arg) throws classnotfoundexception, instantiationexception, illegalaccessexception{                

               //從目前線程取得正在運作的加載器   

        class c2=cl.loadclass("com.zjj.classtest.test").getclass();         //得到類的class對象

        c2.newinstance();               //執行個體化對象

    輸出結果:報錯

    說明:此時loadclass方法加載到記憶體中的類是未連接配接的,當然不會初始化。是以也就沒有“靜态初始化”的輸出。

    測試六:不知道為什麼沒有發現代碼中的classloader存在兩個參數的loadclass方法。

    總結:至此方法對比結束,這篇部落客要是更細緻的了解了jvm加載類的過程和不同方式之間的差別。其實際上隻是封裝的程度不一樣,也就是方法的粒度的差别。當然,有一點内容還沒有通過自己的測試得到驗證,可能是我的方法不對或者是資料有問題。權且記下這個問題!