一、反射的概念 :反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪(fǎng)問(wèn)、檢測和修改它本身狀態(tài)或行為的一種能力。這一概念的提出很快引發(fā)了計算機科學(xué)領(lǐng)域關(guān)于應用反射性的研究。它首先被程序語(yǔ)言的設計領(lǐng)域所采用,并在Lisp和面向對象方面取得了成績(jì)。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射機制的語(yǔ)言。最近,反射機制也被應用到了視窗系統、操作系統和文件系統中。 反射本身并不是一個(gè)新概念,它可能會(huì )使我們聯(lián)想到光學(xué)中的反射概念,盡管計算機科學(xué)賦予了反射概念新的含義,但是,從現象上來(lái)說(shuō),它們確實(shí)有某些相通之處,這些有助于我們的理解。在計算機科學(xué)領(lǐng)域,反射是指一類(lèi)應用,它們能夠自描述和自控制。也就是說(shuō),這類(lèi)應用通過(guò)采用某種機制來(lái)實(shí)現對自己行為的描述(self-representation)和監測(examination),并能根據自身行為的狀態(tài)和結果,調整或修改應用所描述行為的狀態(tài)和相關(guān)的語(yǔ)義??梢钥闯?,同一般的反射概念相比,計算機科學(xué)領(lǐng)域的反射不單單指反射本身,還包括對反射結果所采取的措施。所有采用反射機制的系統(即反射系統)都希望使系統的實(shí)現更開(kāi)放??梢哉f(shuō),實(shí)現了反射機制的系統都具有開(kāi)放性,但具有開(kāi)放性的系統并不一定采用了反射機制,開(kāi)放性是反射系統的必要條件。一般來(lái)說(shuō),反射系統除了滿(mǎn)足開(kāi)放性條件外還必須滿(mǎn)足原因連接(Causally-connected)。所謂原因連接是指對反射系統自描述的改變能夠立即反映到系統底層的實(shí)際狀態(tài)和行為上的情況,反之亦然。開(kāi)放性和原因連接是反射系統的兩大基本要素。13700863760 Java中,反射是一種強大的工具。它使您能夠創(chuàng )建靈活的代碼,這些代碼可以在運行時(shí)裝配,無(wú)需在組件之間進(jìn)行源代表鏈接。反射允許我們在編寫(xiě)與執行時(shí),使我們的程序代碼能夠接入裝載到JVM中的類(lèi)的內部信息,而不是源代碼中選定的類(lèi)協(xié)作的代碼。這使反射成為構建靈活的應用的主要工具。但需注意的是:如果使用不當,反射的成本很高。 二、Java中的類(lèi)反射:Reflection 是 Java 程序開(kāi)發(fā)語(yǔ)言的特征之一,它允許運行中的 Java 程序對自身進(jìn)行檢查,或者說(shuō)“自審”,并能直接操作程序的內部屬性。Java 的這一能力在實(shí)際應用中也許用得不是很多,但是在其它的程序設計語(yǔ)言中根本就不存在這一特性。例如,Pascal、C 或者 C++ 中就沒(méi)有辦法在程序中獲得函數定義相關(guān)的信息。 1.檢測類(lèi): 1.1 reflection的工作機制 考慮下面這個(gè)簡(jiǎn)單的例子,讓我們看看 reflection 是如何工作的。 import java.lang.reflect.*; 按如下語(yǔ)句執行: java DumpMethods java.util.Stack 它的結果輸出為: public java.lang.Object java.util.Stack.push(java.lang.Object) public synchronized java.lang.Object java.util.Stack.pop() public synchronized java.lang.Object java.util.Stack.peek() public boolean java.util.Stack.empty() public synchronized int java.util.Stack.search(java.lang.Object) 這樣就列出了java.util.Stack 類(lèi)的各方法名以及它們的限制符和返回類(lèi)型。 這個(gè)程序使用 Class.forName 載入指定的類(lèi),然后調用 getDeclaredMethods 來(lái)獲取這個(gè)類(lèi)中定義了的方法列表。java.lang.reflect.Methods 是用來(lái)描述某個(gè)類(lèi)中單個(gè)方法的一個(gè)類(lèi)。 1.2 Java類(lèi)反射中的主要方法 對于以下三類(lèi)組件中的任何一類(lèi)來(lái)說(shuō) -- 構造函數、字段和方法 -- java.lang.Class 提供四種獨立的反射調用,以不同的方式來(lái)獲得信息。調用都遵循一種標準格式。以下是用于查找構造函數的一組反射調用: l Constructor getConstructor(Class[] params) -- 獲得使用特殊的參數類(lèi)型的公共構造函數, l Constructor[] getConstructors() -- 獲得類(lèi)的所有公共構造函數 l Constructor getDeclaredConstructor(Class[] params) -- 獲得使用特定參數類(lèi)型的構造函數(與接入級別無(wú)關(guān)) l Constructor[] getDeclaredConstructors() -- 獲得類(lèi)的所有構造函數(與接入級別無(wú)關(guān)) 獲得字段信息的Class 反射調用不同于那些用于接入構造函數的調用,在參數類(lèi)型數組中使用了字段名: l Field getField(String name) -- 獲得命名的公共字段 l Field[] getFields() -- 獲得類(lèi)的所有公共字段 l Field getDeclaredField(String name) -- 獲得類(lèi)聲明的命名的字段 l Field[] getDeclaredFields() -- 獲得類(lèi)聲明的所有字段 用于獲得方法信息函數: l Method getMethod(String name, Class[] params) -- 使用特定的參數類(lèi)型,獲得命名的公共方法 l Method[] getMethods() -- 獲得類(lèi)的所有公共方法 l Method getDeclaredMethod(String name, Class[] params) -- 使用特寫(xiě)的參數類(lèi)型,獲得類(lèi)聲明的命名的方法 l Method[] getDeclaredMethods() -- 獲得類(lèi)聲明的所有方法
1.3開(kāi)始使用 Reflection: 用于 reflection 的類(lèi),如 Method,可以在 java.lang.relfect 包中找到。使用這些類(lèi)的時(shí)候必須要遵循三個(gè)步驟:第一步是獲得你想操作的類(lèi)的 java.lang.Class 對象。在運行中的 Java 程序中,用 java.lang.Class 類(lèi)來(lái)描述類(lèi)和接口等。 下面就是獲得一個(gè) Class 對象的方法之一: Class c = Class.forName("java.lang.String"); 這條語(yǔ)句得到一個(gè) String 類(lèi)的類(lèi)對象。還有另一種方法,如下面的語(yǔ)句: Class c = int.class; 或者 Class c = Integer.TYPE; 它們可獲得基本類(lèi)型的類(lèi)信息。其中后一種方法中訪(fǎng)問(wèn)的是基本類(lèi)型的封裝類(lèi) (如 Integer) 中預先定義好的 TYPE 字段。 第二步是調用諸如 getDeclaredMethods 的方法,以取得該類(lèi)中定義的所有方法的列表。 一旦取得這個(gè)信息,就可以進(jìn)行第三步了——使用 reflection API 來(lái)操作這些信息,如下面這段代碼: Class c = Class.forName("java.lang.String"); Method m[] = c.getDeclaredMethods(); System.out.println(m[0].toString()); 它將以文本方式打印出 String 中定義的第一個(gè)方法的原型。 2.處理對象: 如果要作一個(gè)開(kāi)發(fā)工具像debugger之類(lèi)的,你必須能發(fā)現filed values,以下是三個(gè)步驟: a.創(chuàng )建一個(gè)Class對象 例如: class SampleGet { public static void main(String[] args) { } static void printHeight(Rectangle r) {
三、安全性和反射:在處理反射時(shí)安全性是一個(gè)較復雜的問(wèn)題。反射經(jīng)常由框架型代碼使用,由于這一點(diǎn),我們可能希望框架能夠全面接入代碼,無(wú)需考慮常規的接入限制。但是,在其它情況下,不受控制的接入會(huì )帶來(lái)嚴重的安全性風(fēng)險,例如當代碼在不值得信任的代碼共享的環(huán)境中運行時(shí)。 由于這些互相矛盾的需求,Java編程語(yǔ)言定義一種多級別方法來(lái)處理反射的安全性?;灸J绞菍Ψ瓷鋵?shí)施與應用于源代碼接入相同的限制: n 從任意位置到類(lèi)公共組件的接入 n 類(lèi)自身外部無(wú)任何到私有組件的接入 n 受保護和打包(缺省接入)組件的有限接入 不過(guò)至少有些時(shí)候,圍繞這些限制還有一種簡(jiǎn)單的方法。我們可以在我們所寫(xiě)的類(lèi)中,擴展一個(gè)普通的基本類(lèi)java.lang.reflect.AccessibleObject 類(lèi)。這個(gè)類(lèi)定義了一種setAccessible方法,使我們能夠啟動(dòng)或關(guān)閉對這些類(lèi)中其中一個(gè)類(lèi)的實(shí)例的接入檢測。唯一的問(wèn)題在于如果使用了安全性管理器,它將檢測正在關(guān)閉接入檢測的代碼是否許可了這樣做。如果未許可,安全性管理器拋出一個(gè)例外。 下面是一段程序,在TwoString 類(lèi)的一個(gè)實(shí)例上使用反射來(lái)顯示安全性正在運行: public class ReflectSecurity { public static void main(String[] args) { try { TwoString ts = new TwoString("a", "b"); Field field = clas.getDeclaredField("m_s1"); // field.setAccessible(true); System.out.println("Retrieved value is " + field.get(inst)); } catch (Exception ex) { ex.printStackTrace(System.out); } } } 如果我們編譯這一程序時(shí),不使用任何特定參數直接從命令行運行,它將在field .get(inst)調用中拋出一個(gè)IllegalAccessException異常。如果我們不注釋field.setAccessible(true)代碼行,那么重新編譯并重新運行該代碼,它將編譯成功。最后,如果我們在命令行添加了JVM參數-Djava.security.manager以實(shí)現安全性管理器,它仍然將不能通過(guò)編譯,除非我們定義了ReflectSecurity類(lèi)的許可權限。 四、反射性能:反射是一種強大的工具,但也存在一些不足。一個(gè)主要的缺點(diǎn)是對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿(mǎn)足我們的要求。這類(lèi)操作總是慢于只直接執行相同的操作。 下面的程序是字段接入性能測試的一個(gè)例子,包括基本的測試方法。每種方法測試字段接入的一種形式 -- accessSame 與同一對象的成員字段協(xié)作,accessOther 使用可直接接入的另一對象的字段,accessReflection 使用可通過(guò)反射接入的另一對象的字段。在每種情況下,方法執行相同的計算 -- 循環(huán)中簡(jiǎn)單的加/乘順序。 程序如下: public int accessSame(int loops) { m_value = 0; for (int index = 0; index < loops; index++) { m_value = (m_value + ADDITIVE_VALUE) * MULTIPLIER_VALUE; } return m_value; }
public int accessReference(int loops) { TimingClass timing = new TimingClass(); for (int index = 0; index < loops; index++) { timing.m_value = (timing.m_value + ADDITIVE_VALUE) * MULTIPLIER_VALUE; } return timing.m_value; }
public int accessReflection(int loops) throws Exception { TimingClass timing = new TimingClass(); try { Field field = TimingClass.class. getDeclaredField("m_value"); for (int index = 0; index < loops; index++) { int value = (field.getInt(timing) + ADDITIVE_VALUE) * MULTIPLIER_VALUE; field.setInt(timing, value); } return timing.m_value; } catch (Exception ex) { System.out.println("Error using reflection"); throw ex; } } 在上面的例子中,測試程序重復調用每種方法,使用一個(gè)大循環(huán)數,從而平均多次調用的時(shí)間衡量結果。平均值中不包括每種方法第一次調用的時(shí)間,因此初始化時(shí)間不是結果中的一個(gè)因素。下面的圖清楚的向我們展示了每種方法字段接入的時(shí)間: 我們可以看出:在前兩副圖中(Sun JVM),使用反射的執行時(shí)間超過(guò)使用直接接入的1000倍以上。通過(guò)比較,IBM JVM可能稍好一些,但反射方法仍舊需要比其它方法長(cháng)700倍以上的時(shí)間。任何JVM上其它兩種方法之間時(shí)間方面無(wú)任何顯著(zhù)差異,但IBM JVM幾乎比Sun JVM快一倍。最有可能的是這種差異反映了Sun Hot Spot JVM的專(zhuān)業(yè)優(yōu)化,它在簡(jiǎn)單基準方面表現得很糟糕。反射性能是Sun開(kāi)發(fā)1.4 JVM時(shí)關(guān)注的一個(gè)方面,它在反射方法調用結果中顯示。在這類(lèi)操作的性能方面,Sun 1.4.1 JVM顯示了比1.3.1版本很大的改進(jìn)。 如果為為創(chuàng )建使用反射的對象編寫(xiě)了類(lèi)似的計時(shí)測試程序,我們會(huì )發(fā)現這種情況下的差異不象字段和方法調用情況下那么顯著(zhù)。使用newInstance()調用創(chuàng )建一個(gè)簡(jiǎn)單的java.lang.Object實(shí)例耗用的時(shí)間大約是在Sun 1.3.1 JVM上使用new Object()的12倍,是在IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的兩部。使用Array.newInstance(type, size)創(chuàng )建一個(gè)數組耗用的時(shí)間是任何測試的JVM上使用new type[size]的兩倍,隨著(zhù)數組大小的增加,差異逐步縮小。 結束語(yǔ):Java語(yǔ)言反射提供一種動(dòng)態(tài)鏈接程序組件的多功能方法。它允許程序創(chuàng )建和控制任何類(lèi)的對象(根據安全性限制),無(wú)需提前硬編碼目標類(lèi)。這些特性使得反射特別適用于創(chuàng )建以非常普通的方式與對象協(xié)作的庫。例如,反射經(jīng)常在持續存儲對象為數據庫、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使類(lèi)和數據結構能按名稱(chēng)動(dòng)態(tài)檢索相關(guān)信息,并允許在運行著(zhù)的程序中操作這些信息。Java 的這一特性非常強大,并且是其它一些常用語(yǔ)言,如 C、C++、Fortran 或者 Pascal 等都不具備的。 但反射有兩個(gè)缺點(diǎn)。第一個(gè)是性能問(wèn)題。用于字段和方法接入時(shí)反射要遠慢于直接代碼。性能問(wèn)題的程度取決于程序中是如何使用反射的。如果它作為程序運行中相對很少涉及的部分,緩慢的性能將不會(huì )是一個(gè)問(wèn)題。即使測試中最壞情況下的計時(shí)圖顯示的反射操作只耗用幾微秒。僅反射在性能關(guān)鍵的應用的核心邏輯中使用時(shí)性能問(wèn)題才變得至關(guān)重要。 許多應用中更嚴重的一個(gè)缺點(diǎn)是使用反射會(huì )模糊程序內部實(shí)際要發(fā)生的事情。程序人員希望在源代碼中看到程序的邏輯,反射等繞過(guò)了源代碼的技術(shù)會(huì )帶來(lái)維護問(wèn)題。反射代碼比相應的直接代碼更復雜,正如性能比較的代碼實(shí)例中看到的一樣。解決這些問(wèn)題的最佳方案是保守地使用反射——僅在它可以真正增加靈活性的地方——記錄其在目標類(lèi)中的使用。
利用反射實(shí)現類(lèi)的動(dòng)態(tài)加載 Bromon原創(chuàng ) 請尊重版權 最近在成都寫(xiě)一個(gè)移動(dòng)增值項目,俺負責后臺server端。功能很簡(jiǎn)單,手機用戶(hù)通過(guò)GPRS打開(kāi)Socket與服務(wù)器連接,我則根據用戶(hù)傳過(guò)來(lái)的數據做出響應。做過(guò)類(lèi)似項目的兄弟一定都知道,首先需要定義一個(gè)類(lèi)似于MSNP的通訊協(xié)議,不過(guò)今天的話(huà)題是如何把這個(gè)系統設計得具有高度的擴展性。由于這個(gè)項目本身沒(méi)有進(jìn)行過(guò)較為完善的客戶(hù)溝通和需求分析,所以以后肯定會(huì )有很多功能上的擴展,通訊協(xié)議肯定會(huì )越來(lái)越龐大,而我作為一個(gè)不那么勤快的人,當然不想以后再去修改寫(xiě)好的程序,所以這個(gè)項目是實(shí)踐面向對象設計的好機會(huì )。 首先定義一個(gè)接口來(lái)隔離類(lèi): package org.bromon.reflect; public interface Operator { public java.util.List act(java.util.List params) } 根據設計模式的原理,我們可以為不同的功能編寫(xiě)不同的類(lèi),每個(gè)類(lèi)都繼承Operator接口,客戶(hù)端只需要針對Operator接口編程就可以避免很多麻煩。比如這個(gè)類(lèi): package org.bromon.reflect.*; public class Success implements Operator { public java.util.List act(java.util.List params) { List result=new ArrayList(); |
聯(lián)系客服