標籤:
引述要學習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代碼
- package com.baobaotao.reflect;
- public class Car {
- private String brand;
- private String color;
- private int maxSpeed;
-
- //①預設建構函式
- public Car(){}
-
- //②帶參建構函式
- public Car(String brand,String color,int maxSpeed){
- this.brand = brand;
- this.color = color;
- this.maxSpeed = maxSpeed;
- }
-
- //③未帶參的方法
- public void introduce() {
- System.out.println("brand:"+brand+";color:"+color+";maxSpeed:" +maxSpeed);
- }
- //省略參數的getter/Setter方法
- …
- }
一般情況下,我們會使用如下的代碼建立Car的執行個體:
Java代碼
- Car car = new Car();
- car.setBrand("紅旗CA72");
或者:
Java代碼
- Car car = new Car("紅旗CA72","黑色");
以上兩種方法都採用傳統方式的直接調用目標類的方法,下面我們通過Java反射機制以一種更加通用的方式間接地操作目標類:
代碼清單3-10 ReflectTest
Java代碼
- package com.baobaotao. reflect;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- public class ReflectTest {
- public static Car initByDefaultConst() throws Throwable
- {
- //①通過類裝載器擷取Car類對象
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Class clazz = loader.loadClass("com.baobaotao.reflect.Car");
-
- //②擷取類的預設構造器對象並通過它執行個體化Car
- Constructor cons = clazz.getDeclaredConstructor((Class[])null);
- Car car = (Car)cons.newInstance();
-
-
- //③通過反射方法設定屬性
- Method setBrand = clazz.getMethod("setBrand",String.class);
- setBrand.invoke(car,"紅旗CA72");
- Method setColor = clazz.getMethod("setColor",String.class);
- setColor.invoke(car,"黑色");
- Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);
- setMaxSpeed.invoke(car,200);
- return car;
- }
-
- public static void main(String[] args) throws Throwable {
- Car car = initByDefaultConst();
- car.introduce();
- }
- }
運行以上程式,在控制台上將列印出以下資訊:
引用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中,要經過以下步驟:
[2.]連結:執行校正、準備和解析步驟,其中解析步驟是可以選擇的:
[2.1]校正:檢查載入Class檔案資料的正確性;
[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:[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代碼
- 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
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);
- }
- }
運行該類,列印出以下資訊:
引用drive private car! the color is:紅色
在訪問private、protected成員變數和方法時必須通過setAccessible(boolean access)方法取消Java語言檢查,否則將拋出IllegalAccessException。如果JVM的安全管理器設定了相應的安全機制,調用該方法將拋出SecurityException。
這些文章摘自於我的《Spring 3.x公司專屬應用程式開發實戰》,我將通過連載的方式,陸續在此發出。歡迎大家討論
學習Spring必學的Java基礎知識(1)----反射