類裝載器ClassLoader

類裝載器工做機制 

類裝載器就是尋找類的節碼文件並構造出類在JVM內部表示對象的組件。在Java中,類裝載器把一個類裝入JVM中,要通過如下步驟: java

      [1.]裝載:查找和導入Class文件; 
      [2.]連接:執行校驗、準備和解析步驟,其中解析步驟是能夠選擇的: 
          [2.1]校驗:檢查載入Class文件數據的正確性; 
          [2.2]準備:給類的靜態變量分配存儲空間; 
          [2.3]解析:將符號引用轉成直接引用; 
          [3.]3.初始化:對類的靜態變量、靜態代碼塊執行初始化工做。

類裝載工做由ClassLoader及其子類負責,ClassLoader是一個重要的Java運行時系統組件,它負責在運行時查找和裝入Class字節碼文件。JVM在運行時會產生三個ClassLoader:根裝載器、ExtClassLoader(擴展類裝載器)和AppClassLoader(系統類裝載器)。其中,根裝載器不是ClassLoader的子類,它使用C++編寫,所以咱們在Java中看不到它,根裝載器負責裝載JRE的核心類庫,如JRE目標下的rt.jar、charsets.jar等。ExtClassLoader和AppClassLoader都是ClassLoader的子類。其中ExtClassLoader負責裝載JRE擴展目錄ext中的JAR類包;AppClassLoader負責裝載Classpath路徑下的類包。 

這三個類裝載器之間存在父子層級關係,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。默認狀況下,使用AppClassLoader裝載應用程序的類,咱們能夠作一個實驗: 

代碼清單3-11  ClassLoaderTest 編程

Java代碼
public class ClassLoaderTest {  
    public static void main(String[] args) {  
        ClassLoader loader = Thread.currentThread().getContextClassLoader();  
        System.out.println("current loader:"+loader);  
        System.out.println("parent loader:"+loader.getParent());  
        System.out.println("grandparent loader:"+loader.getParent(). getParent());  
    }  
}

運行以上代碼,在控制檯上將打出如下信息: 數組

引用
current loader:sun.misc.Launcher$AppClassLoader@131f71a 
parent loader:sun.misc.Launcher$ExtClassLoader@15601ea 
     //①根裝載器在Java中訪問不到,因此返回null 
grandparent loader:null

經過以上的輸出信息,咱們知道當前的ClassLoader是AppClassLoader,父ClassLoader是ExtClassLoader,祖父ClassLoader是根類裝載器,由於在Java中沒法得到它的句柄,因此僅返回null。 

JVM裝載類時使用「全盤負責委託機制」,「全盤負責」是指當一個ClassLoader裝載一個類的時,除非顯式地使用另外一個ClassLoader,該類所依賴及引用的類也由這個ClassLoader載入;「委託機制」是指先委託父裝載器尋找目標類,只有在找不到的狀況下才從本身的類路徑中查找並裝載目標類。這一點是從安全角度考慮的,試想若是有人編寫了一個惡意的基礎類(如java.lang.String)並裝載到JVM中將會引發多麼可怕的後果。可是因爲有了「全盤負責委託機制」,java.lang.String永遠是由根裝載器來裝載的,這樣就避免了上述事件的發生。 

ClassLoader重要方法 

在Java中,ClassLoader是一個抽象類,位於java.lang包中。下面對該類的一些重要接口方法進行介紹: 安全

    •   Class loadClass(String name)
    •     name參數指定類裝載器須要裝載類的名字,必須使用全限定類名,如com.baobaotao. beans.Car。該方法有一個重載方法loadClass(String name ,boolean resolve),resolve參數告訴類裝載器是否須要解析該類。在初始化類以前,應考慮進行類解析的工做,但並非全部的類都須要解析,若是JVM只須要知道該類是否存在或找出該類的超類,那麼就不須要進行解析。 
    • Class defineClass(String name, byte[] b, int off, int len)
    •    將類文件的字節數組轉換成JVM內部的java.lang.Class對象。字節數組能夠從本地文件系統、遠程網絡獲取。name爲字節數組對應的全限定類名。 
    •   Class findSystemClass(String name)
    •    從本地文件系統載入Class文件,若是本地文件系統不存在該Class文件,將拋出ClassNotFoundException異常。該方法是JVM默認使用的裝載機制。 
    •   Class findLoadedClass(String name)
    •   調用該方法來查看ClassLoader是否已裝入某個類。若是已裝入,那麼返回java.lang.Class對象,不然返回null。若是強行裝載已存在的類,將會拋出連接錯誤。 
    •   ClassLoader getParent()
    •    獲取類裝載器的父裝載器,除根裝載器外,全部的類裝載器都有且僅有一個父裝載器,ExtClassLoader的父裝載器是根裝載器,由於根裝載器非Java編寫,因此沒法得到,將返回null。 

除JVM默認的三個ClassLoader之外,能夠編寫本身的第三方類裝載器,以實現一些特殊的需求。類文件被裝載並解析後,在JVM內將擁有一個對應的java.lang.Class類描述對象,該類的實例都擁有指向這個類描述對象的引用,而類描述對象又擁有指向關聯ClassLoader的引用,如圖3-4所示。 

 

每個類在JVM中都擁有一個對應的java.lang.Class對象,它提供了類結構信息的描述。數組、枚舉、註解以及基本Java類型(如int、double等),甚至void都擁有對應的Class對象。Class沒有public的構造方法。Class對象是在裝載類時由JVM經過調用類裝載器中的defineClass()方法自動構造的。 

Java反射機制 

Class反射對象描述類語義結構,能夠從Class對象中獲取構造函數、成員變量、方法類等類元素的反射對象,並以編程的方式經過這些反射對象對目標類對象進行操做。這些反射對象類在java.reflect包中定義,下面是最主要的三個反射類: 網絡

    •   Constructor:類的構造函數反射類,經過Class#getConstructors()方法能夠得到類的全部構造函數反射對象數組。在JDK5.0中,還能夠經過getConstructor(Class... parameterTypes)獲取擁有特定入參的構造函數反射對象。Constructor的一個主要方法是newInstance(Object[] initargs),經過該方法能夠建立一個對象類的實例,至關於new關鍵字。在JDK5.0中該方法演化爲更爲靈活的形式:newInstance (Object... initargs)。
    •   Method:類方法的反射類,經過Class#getDeclaredMethods()方法能夠獲取類的全部方法反射類對象數組Method[]。在JDK5.0中能夠經過getDeclaredMethod(String name, Class... parameterTypes)獲取特定簽名的方法,name爲方法名;Class...爲方法入參類型列表。Method最主要的方法是invoke(Object obj, Object[] args),obj表示操做的目標對象;args爲方法入參,代碼清單3 10③處演示了這個反射類的使用方法。在JDK 5.0中,該方法的形式調整爲invoke(Object obj, Object... args)。此外,Method還有不少用於獲取類方法更多信息的方法:
    •       1)Class getReturnType():獲取方法的返回值類型; 
            2)Class[] getParameterTypes():獲取方法的入參類型數組; 
            3)Class[] getExceptionTypes():獲取方法的異常類型數組; 
          4)Annotation[][] getParameterAnnotations():獲取方法的註解信息,JDK 5.0中的新方法;
  •   Field:類的成員變量的反射類,經過Class#getDeclaredFields()方法能夠獲取類的成員變量反射對象數組,經過Class#getDeclaredField(String name)則可獲取某個特定名稱的成員變量反射對象。Field類最主要的方法是set(Object obj, Object value),obj表示操做的目標對象,經過value爲目標對象的成員變量設置值。若是成員變量爲基礎類型,用戶能夠使用Field類中提供的帶類型名的值設置方法,如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。

此外,Java還爲包提供了Package反射類,在JDK 5.0中還爲註解提供了AnnotatedElement反射類。總之,Java的反射體系保證了能夠經過程序化的方式訪問目標類中全部的元素,對於private或protected的成員變量和方法,只要JVM的安全機制容許,也能夠經過反射進行調用,請看下面的例子: 
代碼清單3-12  PrivateCarReflect 函數

Java代碼  
package com.baobaotao.reflect;  
public class PrivateCar {  
       //①private成員變量:使用傳統的類實例調用方式,只能在本類中訪問  
   private String color;   
        //②protected方法:使用傳統的類實例調用方式,只能在子類和本包中訪問  
   protected void drive(){  
         
System.out.println("drive private car! the color is:"+color);  
   }  
}  

color變量和drive()方法都是私有的,經過類實例變量沒法在外部訪問私有變量、調用私有方法的,但經過反射機制卻能夠繞過這個限制: 

代碼清單3-13  PrivateCarReflect spa

Java代碼
public class PrivateCarReflect {  
   public static void main(String[] args) throws Throwable{  
       ClassLoader loader = Thread.currentThread().getContextClassLoader();  
       Class clazz = loader.loadClass("com.baobaotao.reflect.PrivateCar");  
       PrivateCar pcar = (PrivateCar)clazz.newInstance();  
         
       Field colorFld = clazz.getDeclaredField("color");  
        //①取消Java語言訪問檢查以訪問private變量  
       colorFld.setAccessible(true);   
       colorFld.set(pcar,"紅色");  
         
       Method driveMtd = clazz.getDeclaredMethod("drive",(Class[])null);  
        //Method driveMtd = clazz.getDeclaredMethod("drive"); JDK5.0下使用  
         
        //②取消Java語言訪問檢查以訪問protected方法  
       driveMtd.setAccessible(true);   
        driveMtd.invoke(pcar,(Object[])null);  
  }  
} 

運行該類,打印出如下信息: code

引用
drive private car! the color is:紅色


在訪問private、protected成員變量和方法時必須經過setAccessible(boolean access)方法取消Java語言檢查,不然將拋出IllegalAccessException。若是JVM的安全管理器設置了相應的安全機制,調用該方法將拋出SecurityException。 對象

相關文章
相關標籤/搜索