學習Spring必學的Java基礎知識(1)----反射

來源:互聯網
上載者:User

標籤:

引述要學習Spring架構的技術內幕,必須事先掌握一些基本的Java知識,正所謂“登高必自卑,涉遠必自邇”。以下幾項Java知識和Spring架構息息相關,不可不學(我將通過一個系列分別介紹這些Java基礎知識,希望對大家有所協助。): 

[1] Java反射知識-->Spring IoC :http://www.iteye.com/topic/1123081 
[2] Java動態代理-->Spring AOP :http://www.iteye.com/topic/1123293 
[3] 屬性編輯器,即PropertyEditor-->Spring IoC:http://www.iteye.com/topic/1123628 
[4] XML基礎知識-->Spring配置:http://www.iteye.com/topic/1123630 
[5] 註解-->Spring配置:http://www.iteye.com/topic/1123823 
[6] 執行緒區域變更,即ThreadLocal-->Spring交易管理:http://www.iteye.com/topic/1123824 
[7] 事務基礎知識-->Spring交易管理:http://www.iteye.com/topic/1124043 
[8] 國際化資訊-->MVC:http://www.iteye.com/topic/1124044 
[9] HTTP報文-->MVC:http://www.iteye.com/topic/1124408 




    Java語言允許通過程式化的方式間接對Class進行操作,Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊對象,通過該元資訊對象可以獲知Class的結構資訊:如建構函式、屬性和方法等。Java允許使用者藉由這個Class相關的元資訊對象間接調用Class對象的功能,這就為使用程式化方式操作Class對象開闢了途徑。 

簡單一實例 

    我們將從一個簡單例子開始探訪Java反射機制的征程,下面的Car類擁有兩個建構函式、兩個方法以及三個屬性,如代碼清單3-9所示: 

代碼清單3-9  Car 

Java代碼  
  1. package com.baobaotao.reflect;  
  2. public class Car {  
  3.     private String brand;  
  4.     private String color;  
  5.     private int maxSpeed;  
  6.       
  7.      //①預設建構函式  
  8.     public Car(){}  
  9.        
  10.      //②帶參建構函式  
  11.     public Car(String brand,String color,int maxSpeed){   
  12.         this.brand = brand;  
  13.         this.color = color;  
  14.         this.maxSpeed = maxSpeed;  
  15.     }  
  16.   
  17.      //③未帶參的方法  
  18.     public void introduce() {   
  19.        System.out.println("brand:"+brand+";color:"+color+";maxSpeed:" +maxSpeed);  
  20.     }  
  21.      //省略參數的getter/Setter方法  
  22.      …  
  23. }  


一般情況下,我們會使用如下的代碼建立Car的執行個體: 

Java代碼  
  1. Car car = new Car();  
  2. car.setBrand("紅旗CA72");  


或者: 

Java代碼  
  1. Car car = new Car("紅旗CA72","黑色");  



以上兩種方法都採用傳統方式的直接調用目標類的方法,下面我們通過Java反射機制以一種更加通用的方式間接地操作目標類: 

代碼清單3-10  ReflectTest 

Java代碼  
  1. package com.baobaotao. reflect;  
  2. import java.lang.reflect.Constructor;  
  3. import java.lang.reflect.Field;  
  4. import java.lang.reflect.Method;  
  5. public class ReflectTest {  
  6.     public static Car  initByDefaultConst() throws Throwable  
  7.     {  
  8.         //①通過類裝載器擷取Car類對象  
  9.         ClassLoader loader = Thread.currentThread().getContextClassLoader();   
  10.         Class clazz = loader.loadClass("com.baobaotao.reflect.Car");   
  11.           
  12.           //②擷取類的預設構造器對象並通過它執行個體化Car  
  13.         Constructor cons = clazz.getDeclaredConstructor((Class[])null);   
  14.         Car car = (Car)cons.newInstance();  
  15.           
  16.            
  17.           //③通過反射方法設定屬性  
  18.         Method setBrand = clazz.getMethod("setBrand",String.class);          
  19.         setBrand.invoke(car,"紅旗CA72");        
  20.         Method setColor = clazz.getMethod("setColor",String.class);  
  21.         setColor.invoke(car,"黑色");        
  22.         Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);  
  23.         setMaxSpeed.invoke(car,200);          
  24.         return car;  
  25.     }  
  26.   
  27.     public static void main(String[] args) throws Throwable {  
  28.         Car car = initByDefaultConst();  
  29.         car.introduce();  
  30.     }  
  31. }  


運行以上程式,在控制台上將列印出以下資訊: 

引用brand:紅旗CA72;color:黑色;maxSpeed:200



這說明我們完全可以通過編程方式調用Class的各項功能,這和直接通過建構函式和方法調用類功能的效果是一致的,只不過前者是間接調用,後者是直接調用罷了。 

在ReflectTest中,使用了幾個重要的反射類,分別是ClassLoader、Class、Constructor和Method,通過這些反射類就可以間接調用目標Class的各項功能了。在①處,我們擷取當前線程的ClassLoader,然後通過指定的全限定類“com.baobaotao.beans.Car”裝載Car類對應的反射執行個體。在②處,我們通過Car的反射類對象擷取Car的建構函式對象cons,通過建構函式對象的newInstrance()方法執行個體化Car對象,其效果等同於new Car()。在③處,我們又通過Car的反射類對象的getMethod(String methodName,Class paramClass)擷取屬性的Setter方法對象,第一個參數是目標Class的方法名;第二個參數是方法入參的物件類型。擷取方法反射對象後,即可通過invoke(Object obj,Object param)方法調用目標類的方法,該方法的第一個參數是操作的目標類對象執行個體;第二個參數是目標方法的入參。 

在代碼清單3 10中,粗體所示部分的資訊即是通過反射方法操控目標類的元資訊,如果我們將這些資訊以一個設定檔的方式提供,就可以使用Java語言的反射功能編寫一段通用的代碼對類似於Car的類進行執行個體化及功能叫用作業了。 

類裝載器ClassLoader 

類裝載器工作機制 

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

      [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代碼  
  1. public class ClassLoaderTest {  
  2.     public static void main(String[] args) {  
  3.         ClassLoader loader = Thread.currentThread().getContextClassLoader();  
  4.         System.out.println("current loader:"+loader);  
  5.         System.out.println("parent loader:"+loader.getParent());  
  6.         System.out.println("grandparent loader:"+loader.getParent(). getParent());  
  7.     }  
  8. }  


運行以上代碼,在控制台上將打出以下資訊: 

引用current loader:[email protected] 
parent loader:[email protected] 
     //①根裝載器在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代碼  
  1. package com.baobaotao.reflect;  
  2. public class PrivateCar {  
  3.        //①private成員變數:使用傳統的類執行個體調用方式,只能在本類中訪問  
  4.    private String color;   
  5.         //②protected方法:使用傳統的類執行個體調用方式,只能在子類和本包中訪問  
  6.    protected void drive(){  
  7.          
  8. System.out.println("drive private car! the color is:"+color);  
  9.    }  
  10. }  


color變數和drive()方法都是私人的,通過類執行個體變數無法在外部存取私人變數、調用私人方法的,但通過反射機制卻可以繞過這個限制: 

代碼清單3-13  PrivateCarReflect 

Java代碼  
  1. …  
  2. public class PrivateCarReflect {  
  3.    public static void main(String[] args) throws Throwable{  
  4.        ClassLoader loader = Thread.currentThread().getContextClassLoader();  
  5.        Class clazz = loader.loadClass("com.baobaotao.reflect.PrivateCar");  
  6.        PrivateCar pcar = (PrivateCar)clazz.newInstance();  
  7.          
  8.        Field colorFld = clazz.getDeclaredField("color");  
  9.         //①取消Java語言訪問檢查以訪問private變數  
  10.        colorFld.setAccessible(true);   
  11.        colorFld.set(pcar,"紅色");  
  12.          
  13.        Method driveMtd = clazz.getDeclaredMethod("drive",(Class[])null);  
  14.         //Method driveMtd = clazz.getDeclaredMethod("drive"); JDK5.0下使用  
  15.          
  16.         //②取消Java語言訪問檢查以訪問protected方法  
  17.        driveMtd.setAccessible(true);   
  18.         driveMtd.invoke(pcar,(Object[])null);  
  19.   }  
  20. }  


運行該類,列印出以下資訊: 

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



在訪問private、protected成員變數和方法時必須通過setAccessible(boolean access)方法取消Java語言檢查,否則將拋出IllegalAccessException。如果JVM的安全管理器設定了相應的安全機制,調用該方法將拋出SecurityException。 

這些文章摘自於我的《Spring 3.x公司專屬應用程式開發實戰》,我將通過連載的方式,陸續在此發出。歡迎大家討論

學習Spring必學的Java基礎知識(1)----反射

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.