Java反射機(jī)制的學(xué)習(xí)
Java反射機(jī)制的學(xué)習(xí)
Java反射機(jī)制是Java語(yǔ)言被視為準(zhǔn)動(dòng)態(tài)語(yǔ)言的關(guān)鍵性質(zhì)。Java反射機(jī)制的核心就是允許在運(yùn)行時(shí)通過(guò)Java Reflection APIs來(lái)取得已知名字的class類的相關(guān)信息,動(dòng)態(tài)地生成此類,并調(diào)用其方法或修改其域(甚至是本身聲明為private的域或方法)。
也許你使用Java已經(jīng)很長(zhǎng)時(shí)間了,可是幾乎不會(huì)用到Java反射機(jī)制。你會(huì)嗤之以鼻地告訴我,Java反射機(jī)制沒(méi)啥用。或許在J2EE、J2SE等平臺(tái),Java反射機(jī)制沒(méi)啥用(具體我也不了解,不多做評(píng)論),但是在Android應(yīng)用開(kāi)發(fā)中,該機(jī)制會(huì)帶給你許多驚喜。
如果熟悉Android,那么你應(yīng)該知道,Google不知出于什么原因,在系統(tǒng)源碼中一些類或方法中經(jīng)常加上“@hide”注釋標(biāo)記。它的作用是使這個(gè)方法或類在生成SDK時(shí)不可見(jiàn),因此由此注釋的東西,你在編譯期是不可見(jiàn)的。這就出現(xiàn)了一些問(wèn)題。一些明明可以訪問(wèn)的東西編譯期卻無(wú)法訪問(wèn)了!這使得你的程序有些本來(lái)可以完成的功能無(wú)法編譯通過(guò)。
當(dāng)然,有一種辦法是自己去掉Android源碼中的所有“@hide”標(biāo)記,然后重新編譯一份自己的SDK。另一種辦法就是使用Java反射機(jī)制。當(dāng)然,你還可以利用反射來(lái)訪問(wèn)存在訪問(wèn)限制的方法和修改其域。不過(guò)這種使用方法比較特殊,我們?cè)谖恼碌淖詈髥为?dú)討論。
從Class類說(shuō)起
如果你使用Java,那么你應(yīng)該知道Java中有一個(gè)Class類。Class類本身表示Java對(duì)象的類型,我們可以通過(guò)一個(gè)Object(子)對(duì)象的getClass方法取得一個(gè)對(duì)象的類型,此函數(shù)返回的就是一個(gè)Class類。當(dāng)然,獲得Class對(duì)象的方法有許多,但是沒(méi)有一種方法是通過(guò)Class的構(gòu)造函數(shù)來(lái)生成Class對(duì)象的。
也許你從來(lái)沒(méi)有使用過(guò)Class類,也許你曾以為這是一個(gè)沒(méi)什么用處的東西。不管你以前怎么認(rèn)為,Class類是整個(gè)Java反射機(jī)制的源頭。一切關(guān)于Java反射的故事,都從Class類開(kāi)始。
因此,要想使用Java反射,我們首先得到Class類的對(duì)象。下表列出了幾種得到Class類的方法,以供大家參考。
Class object 誕生管道 |
示例 |
運(yùn)用getClass() 注:每個(gè)class 都有此函數(shù) |
String str = "abc"; Class c1 = str.getClass(); |
運(yùn)用 Class.getSuperclass() |
Button b = new Button(); Class c1 = b.getClass(); Class c2 = c1.getSuperclass(); |
運(yùn)用static method Class.forName() (最常被使用) |
Class c1 = Class.forName ("java.lang.String"); Class c2 = Class.forName ("java.awt.Button"); Class c3 = Class.forName ("java.util.LinkedList$Entry"); Class c4 = Class.forName ("I"); Class c5 = Class.forName ("[I"); |
運(yùn)用 .class 語(yǔ)法 |
Class c1 = String.class; Class c2 = java.awt.Button.class; Class c3 = Main.InnerClass.class; Class c4 = int.class; Class c5 = int[].class; |
運(yùn)用 primitive wrapper classes 的TYPE 語(yǔ)法 |
Class c1 = Boolean.TYPE; Class c2 = Byte.TYPE; Class c3 = Character.TYPE; Class c4 = Short.TYPE; Class c5 = Integer.TYPE; Class c6 = Long.TYPE; Class c7 = Float.TYPE; Class c8 = Double.TYPE; Class c9 = Void.TYPE; |
獲取一些基本信息
在我們得到一個(gè)類的Class類對(duì)象之后,Java反射機(jī)制就可以大施拳腳了。首先讓我們來(lái)了解下如何獲取關(guān)于某一個(gè)類的一些基本信息。
Java class 內(nèi)部模塊 |
Java class 內(nèi)部模塊說(shuō)明 |
相應(yīng)之Reflection API,多半為Class methods。 |
返回值類型(return type) |
package |
class隸屬哪個(gè)package |
getPackage() |
Package |
import |
class導(dǎo)入哪些classes |
無(wú)直接對(duì)應(yīng)之API。可間接獲取。 |
|
modifier |
class(或methods, fields)的屬性 |
int getModifiers() Modifier.toString(int) Modifier.isInterface(int) |
int String bool |
class name or interface name |
class/interface |
名稱getName() |
String |
type parameters |
參數(shù)化類型的名稱 |
getTypeParameters() |
TypeVariable <Class>[] |
base class |
base class(只可能一個(gè)) |
getSuperClass() |
Class |
implemented interfaces |
實(shí)現(xiàn)有哪些interfaces |
getInterfaces() |
Class[] |
inner classes |
內(nèi)部classes |
getDeclaredClasses() |
Class[] |
outer class |
如果我們觀察的class 本身是inner classes,那么相對(duì)它就會(huì)有個(gè)outer class。 |
getDeclaringClass() |
Class |
上表中,列出了一些Java class內(nèi)部信息的獲取方式。所采用的方法幾乎都是調(diào)用Class對(duì)象的成員方法(由此你就可以了解到Class類的用處了吧)。當(dāng)然,表中所列出的信息并不是全部,有很大一部分沒(méi)有列出,你可以通過(guò)查閱Java文檔得到更全面的了解。另外,下面將重點(diǎn)介紹一下類的構(gòu)造函數(shù)、域和成員方法的獲取方式。
類中最重要的三個(gè)信息
如果要對(duì)一個(gè)類的信息重要性進(jìn)行排名的話,那么這三個(gè)信息理應(yīng)獲得前三的名次。它們分別是:構(gòu)造函數(shù)、成員函數(shù)、成員變量。
也許你不同意我的排名,沒(méi)關(guān)系。對(duì)于Java反射來(lái)說(shuō),這三個(gè)信息與之前介紹的基本信息相比較而言,有著本質(zhì)的區(qū)別。那就是,之前的信息僅僅是只讀的,而這三個(gè)信息可以在運(yùn)行時(shí)被調(diào)用(構(gòu)造函數(shù)和成員函數(shù))或者被修改(成員變量)。所以,我想無(wú)可否認(rèn),至少站在Java反射機(jī)制的立場(chǎng)來(lái)說(shuō),這三者是最重要的信息。
下面,讓我們分別了解一下這三個(gè)重要信息的獲取方式。另外,我們將在后面的章節(jié),詳細(xì)介紹他們的調(diào)用方式或者修改方式。
構(gòu)造函數(shù)
如果我們將Java對(duì)象視為一個(gè)二進(jìn)制的生活在內(nèi)存中生命體的話,那么構(gòu)造函數(shù)無(wú)疑可以類比為Java對(duì)象生命體的誕生過(guò)程。我們?cè)跇?gòu)造函數(shù)調(diào)用時(shí)為對(duì)象分配內(nèi)存空間,初始化一些屬性,于是一個(gè)新的生命誕生了。
Java是純面向?qū)ο蟮恼Z(yǔ)言,Java中幾乎所有的一切都是類的對(duì)象,因此可想而知構(gòu)造函數(shù)的重要性。
Java反射機(jī)制能夠得到構(gòu)造函數(shù)信息實(shí)在應(yīng)該是一件令人驚喜的事情。正因?yàn)榇耍瓷錂C(jī)制實(shí)質(zhì)上才擁有了孵化生命的能力。換句話言之,我們可以通過(guò)反射機(jī)制,動(dòng)態(tài)地創(chuàng)建新的對(duì)象。
獲取構(gòu)造函數(shù)的方法有以下幾個(gè):
Constructor getConstructor(Class[] params)
Constructor[] getConstructors()
Constructor getDeclaredConstructor(Class[] params)
Constructor[] getDeclaredConstructors()
我們有兩種方式對(duì)這四個(gè)函數(shù)分組。
首先可以由構(gòu)造函數(shù)的確定性進(jìn)行分類。我們知道,一個(gè)類實(shí)際上可以擁有很多個(gè)構(gòu)造函數(shù)。那么我們獲取的構(gòu)造函數(shù)是哪個(gè)呢?我們可以根據(jù)構(gòu)造函數(shù)的參數(shù)標(biāo)簽對(duì)構(gòu)造函數(shù)進(jìn)行明確的區(qū)分,因此,如果我們?cè)?font face="Times New Roman">Java反射時(shí)指定構(gòu)造函數(shù)的參數(shù),那么我們就能確定地返回我們需要的那個(gè)“唯一”的構(gòu)造函數(shù)。getConstructor(Class[] params) 和getDeclaredConstructor(Class[] params)正是這種確定唯一性的方式。但是,如果我們不清楚每個(gè)構(gòu)造函數(shù)的參數(shù)表,或者我們出于某種目的需要獲取所有的構(gòu)造函數(shù)的信息,那么我們就不需要明確指定參數(shù)表,而這時(shí)返回的就應(yīng)該是構(gòu)造函數(shù)數(shù)組,因?yàn)闃?gòu)造函數(shù)很可能不止一個(gè)。getConstructors()和getDeclaredConstructors()就是這種方式。
另外,我們還可以通過(guò)構(gòu)造函數(shù)的訪問(wèn)權(quán)限進(jìn)行分類。在設(shè)計(jì)類的時(shí)候,我們往往有一些構(gòu)造函數(shù)需要聲明為“private”、“protect”或者“default”,目的是為了不讓外部的類調(diào)用此構(gòu)造函數(shù)生成對(duì)象。于是,基于訪問(wèn)權(quán)限的不同,我們可以將構(gòu)造函數(shù)分為public和非public兩種。
getConstructor(Class[] params) 和getConstructors()僅僅可以獲取到public的構(gòu)造函數(shù),而getDeclaredConstructor(Class[] params) 和getDeclaredConstructors()則能獲取所有(包括public和非public)的構(gòu)造函數(shù)。
成員函數(shù)
如果構(gòu)造函數(shù)類比為對(duì)象的誕生過(guò)程的話,成員函數(shù)無(wú)疑可以類比為對(duì)象的生命行為過(guò)程。成員函數(shù)的調(diào)用執(zhí)行才是絕大多數(shù)對(duì)象存在的證據(jù)和意義。Java反射機(jī)制允許獲取成員函數(shù)(或者說(shuō)成員方法)的信息,也就是說(shuō),反射機(jī)制能夠幫助對(duì)象踐行生命意義。通俗地說(shuō),Java反射能使對(duì)象完成其相應(yīng)的功能。
和獲取構(gòu)造函數(shù)的方法類似,獲取成員函數(shù)的方法有以下一些:
Method getMethod(String name, Class[] params)
Method[] getMethods()
Method getDeclaredMethod(String name, Class[] params)
Method[] getDeclaredMethods()
其中需要注意,String name參數(shù),需要寫入方法名。關(guān)于訪問(wèn)權(quán)限和確定性的問(wèn)題,和構(gòu)造函數(shù)基本一致。
成員變量
成員變量,我們經(jīng)常叫做一個(gè)對(duì)象的域。從內(nèi)存的角度來(lái)說(shuō),構(gòu)造函數(shù)和成員函數(shù)都僅僅是Java對(duì)象的行為或過(guò)程,而成員變量則是真正構(gòu)成對(duì)象本身的細(xì)胞和血肉。簡(jiǎn)單的說(shuō),就是成員變量占用的空間之和幾乎就是對(duì)象占用的所有內(nèi)存空間。
獲取成員變量的方法與上面兩種方法類似,具體如下:
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
其中,String name參數(shù),需要寫入變量名。關(guān)于訪問(wèn)權(quán)限和確定性的問(wèn)題,與前面兩例基本一致。
讓動(dòng)態(tài)真正動(dòng)起來(lái)
在本文的一開(kāi)始就說(shuō),Java反射機(jī)制是Java語(yǔ)言被視為準(zhǔn)動(dòng)態(tài)語(yǔ)言的關(guān)鍵性質(zhì)。如果Java反射僅僅能夠得到Java類(或?qū)ο螅┻\(yùn)行時(shí)的信息,而不能改變其行為和屬性,那么它當(dāng)然算不上“動(dòng)態(tài)”。百度了一把何謂“動(dòng)態(tài)語(yǔ)言”,解釋如下:動(dòng)態(tài)語(yǔ)言,是指程序在運(yùn)行時(shí)可以改變其結(jié)構(gòu):新的函數(shù)可以被引進(jìn),已有的函數(shù)可以被刪除等在結(jié)構(gòu)上的變化。由此看來(lái),Java確實(shí)不能算作“動(dòng)態(tài)語(yǔ)言”。但是和C、C++等純靜態(tài)語(yǔ)言相比,Java語(yǔ)言允許使用者在運(yùn)行時(shí)加載、探知、使用編譯期間完全未知的classes,所以我們說(shuō)Java是“準(zhǔn)動(dòng)態(tài)”語(yǔ)言。
細(xì)心地讀者可能已經(jīng)發(fā)現(xiàn),在“類中最重要的三個(gè)信息”一節(jié)中,我們獲取的信息其實(shí)都是屬于類的,而不是對(duì)象。對(duì)于類的信息提取,其實(shí)并不涉及到對(duì)象內(nèi)存,在程序編譯完成的那一刻起,一切都已經(jīng)是確定的了。因此,它并不能算“動(dòng)態(tài)”。而如何對(duì)對(duì)象內(nèi)存進(jìn)行操作和訪問(wèn),才是“動(dòng)”的真正含義。
說(shuō)了這么多,關(guān)鍵還在于如何利用反射讓Java真正動(dòng)起來(lái)。下面我將按照創(chuàng)生、行為與屬性三個(gè)方面來(lái)介紹反射機(jī)制是如何讓Java動(dòng)的。
創(chuàng)生
不知是否本性使然,人類偏愛(ài)于思索起源與終結(jié)的話題。如果將程序類比于一個(gè)二進(jìn)制的世界的話,那么我們程序員則是這個(gè)世界的上帝。我們掌控著這個(gè)世界的起源和終結(jié),熟悉世界中一草一木的屬性和所有生靈的習(xí)性。現(xiàn)在就讓我們開(kāi)始創(chuàng)世紀(jì)吧!
在 “構(gòu)造函數(shù)”那一小節(jié)中,我們列出了獲取構(gòu)造函數(shù)的四種方法。這四種方法的返回值不知是否引起了各位的注意,那就是Constructor類。Constructor就類比于女媧吹給泥人的那一口真氣,有了它,一個(gè)生命才真正出現(xiàn)。
Constructor支持泛型,也就是它本身應(yīng)該是Constructor<T>。這個(gè)類有一個(gè)public成員函數(shù),T newInstance(Object... args),其中args為對(duì)應(yīng)的參數(shù)。我們正是通過(guò)它來(lái)實(shí)現(xiàn)創(chuàng)生的過(guò)程。
行為
行為踐行著生命的意義,而眾多事物的行為才得以構(gòu)成整個(gè)世界的運(yùn)轉(zhuǎn)。盡管道家的老子主張“無(wú)為而治”,宣揚(yáng)“圣人處無(wú)為之事,行不言之教”,但那是因?yàn)樗旧砭褪?nbsp;“無(wú)”的信仰者(“道”即“無(wú)”)。我們是唯物主義的信徒,所以必然要以“有”為價(jià)值。那么,在二進(jìn)制的世界里,我們?nèi)绾握{(diào)用Java對(duì)象的行為呢?
同樣,我們首先回顧“成員函數(shù)”小節(jié)中四種方法的返回值。對(duì),那就是Method類。此類有一個(gè)public成員函數(shù),Object invoke(Object receiver, Object... args)。我們能很好理解此函數(shù)的第二個(gè)參數(shù)args,它代表這個(gè)方法所需要接收的參數(shù)。也許大家對(duì)第一個(gè)參數(shù)receiver還存在疑惑之處。這得從編程語(yǔ)言的發(fā)展歷程講起。
如果你關(guān)注幾種主流編程語(yǔ)言的起源,那么你能有這樣的印象:C從匯編而來(lái),C++從C而來(lái),而Java從C/C++而來(lái)。有這樣一種印象就足夠了。從這樣的發(fā)展史我們可以看出,C++和Java這兩種面向?qū)ο蟮木幊陶Z(yǔ)言都是從面向過(guò)程的C語(yǔ)言基礎(chǔ)上發(fā)展而來(lái)的。OOP是一種思想,它本身與編程語(yǔ)言無(wú)關(guān)。也就是說(shuō),我們用C也能寫出面向?qū)ο蟮某绦颍@也是C++和Java能夠以C為基礎(chǔ)的根本所在。然而,C無(wú)法實(shí)現(xiàn)類似object.method()這種表現(xiàn)形式,因?yàn)?/font>C語(yǔ)言的結(jié)構(gòu)體中并不支持函數(shù)定義。那么我們用C實(shí)現(xiàn)OOP的時(shí)候,如何調(diào)用對(duì)象的方法呢?
本質(zhì)上說(shuō),object.method()這種調(diào)用方式是為了表明具體method()的調(diào)用對(duì)象。而invoke(Object receiver, Object... args)的第一個(gè)參數(shù)正是指明調(diào)用對(duì)象。在C++中,object.method()其實(shí)是有隱含參數(shù)的,那就是object對(duì)象的指針,method原型的第一個(gè)參數(shù)其實(shí)是this指針,于是原型為method(void* this)。
這樣一溯源,也許你更清楚了Object receiver參數(shù)的含義,或許更迷糊了?不管怎樣,歷史就是如此,只不過(guò)我個(gè)人能力有限,說(shuō)不清楚而已。
另外需要注意的是,如果某個(gè)方法是Java類的靜態(tài)方法,那么Object receiver參數(shù)可以傳入null,因?yàn)殪o態(tài)方法不從屬于對(duì)象。
屬性
同樣是人類,令狐沖和岳不群是如何被區(qū)分開(kāi)的?那是因?yàn)樗麄冇兄煌膶傩浴M瑯樱粋€(gè)類可以生成多個(gè)對(duì)象,幾個(gè)同類型的對(duì)象之間如何區(qū)分?屬性起著決定性的作用。說(shuō)到這里,想起一個(gè)科幻故事。人體瞬移機(jī),作用的根本原理就是人進(jìn)入A位置,被完全掃描之后,再在B位置重新組成它的細(xì)胞、血肉等屬性,從而完全創(chuàng)造出另一個(gè)一模一樣的人。當(dāng)然,這是唯物主義的極致,它假設(shè)了只要一切物質(zhì)相同,連記憶和靈魂都不會(huì)出現(xiàn)偏差,另外還存在倫理的問(wèn)題,例如A位置的人會(huì)被銷毀掉嗎?
盡管這是一個(gè)科幻,但是在程序的世界里,我們?cè)缫呀?jīng)用上了這類似幻想的技術(shù)。Java中如何遠(yuǎn)程傳遞一個(gè)對(duì)象?我們已經(jīng)使用上了Java對(duì)象序列化的接口。不僅如此,利用序列化接口,我們甚至可以將一個(gè)生命保存起來(lái),在需要的時(shí)候?qū)⑺鼜?fù)活,這就是對(duì)象的持久化。不得不感慨,在程序的世界里,我們就是上帝啊!
對(duì)象序列化如此強(qiáng)大,那么它的本質(zhì)是什么呢?它的工作原理是怎樣的呢?簡(jiǎn)單的說(shuō),對(duì)象序列化的本質(zhì)就是屬性的序列化。原理就是我們崇尚的唯物主義,如果同一個(gè)類的兩個(gè)對(duì)象所有屬性值都完全相同,那么我們可以認(rèn)為這是同一個(gè)對(duì)象。
說(shuō)了這么多,只是想說(shuō)明一件事情,屬性對(duì)于對(duì)象而言是多么的重要。那么如何讀寫對(duì)象中屬性的值呢?回顧獲取屬性信息的方法返回值類型,那是Field。Field類有兩個(gè)public方法,分別對(duì)應(yīng)讀與寫,它們是:
Object get(Object object)
void set(Object object, Object value)
object參數(shù)需要傳入的對(duì)象,原理類似于成員方法需要指明對(duì)象一樣。如果是靜態(tài)屬性,此值同樣可以為null。
關(guān)于反射的一些高級(jí)話題
如果說(shuō)前面那些屬于Java反射的基本知識(shí),那么在文章的最后,我們來(lái)探討一下反射的一些高級(jí)話題。另外,本文對(duì)基礎(chǔ)知識(shí)的講解僅屬于抓主干,具體的一些旁支可以自己參看文檔。需要提一下的是,Java反射中對(duì)數(shù)組做過(guò)單獨(dú)的優(yōu)化處理,具體可查看java.lang.reflect.Array類;還有關(guān)于泛型的支持,可查看java.lang.reflect.ParameterizedType及相關(guān)資料。
暫時(shí)想到的高級(jí)話題有三個(gè),由于對(duì)Java反射理解的也不算深入,所以僅僅從思路上進(jìn)行探討,具體實(shí)現(xiàn)上,大家可以參考其他相關(guān)資料,做更深入研究。
Android編譯期問(wèn)題
Android的安全權(quán)限問(wèn)題我把它簡(jiǎn)單的劃分成三個(gè)層次,最不嚴(yán)格的一層就是僅僅騙過(guò)編譯器的“@hide”標(biāo)記。對(duì)于一款開(kāi)源的操作系統(tǒng)而言,這個(gè)標(biāo)記本身并不具備安全上的限制。不過(guò),從上次Google過(guò)來(lái)的負(fù)責(zé)Android工程師的說(shuō)法來(lái)看,這個(gè)標(biāo)記的作用更多的是方便硬件廠商做閉源的二次開(kāi)發(fā)。這樣解釋倒也說(shuō)得過(guò)去。
不過(guò)這并不影響我們使用反射機(jī)制以繞過(guò)原生Android的第一層安全措施。如果你熟悉源碼的話,會(huì)發(fā)現(xiàn)這可以應(yīng)用到很多地方。并且最關(guān)鍵的是你并不需要放在源碼中編譯,而是像普通應(yīng)用程序的開(kāi)發(fā)過(guò)程一樣。
具體使用范圍我不能一一列舉了,例如自定義窗口、安裝程序等等。簡(jiǎn)單的說(shuō),在Android上使用反射技術(shù),你才會(huì)對(duì)Android系統(tǒng)有更深的理解和更高的控制權(quán)。
軟件的解耦合
我們?cè)诩軜?gòu)代碼的時(shí)候,經(jīng)常提到解耦合、弱耦合。其實(shí),解耦和不僅僅只能在代碼上做文章。我們可以考慮這樣一種情況:軟件的功能需求不可能一開(kāi)始就完全確定,有一些功能在軟件開(kāi)發(fā)的后期甚至是軟件已經(jīng)發(fā)布出去之后才想到要加入或者去掉。
按我們慣有的思維,這種情況就得改動(dòng)源碼,重新編譯。如果軟件已經(jīng)發(fā)布出去,那么就得讓客戶重新安裝一次軟件。反思一下,我們是否認(rèn)為軟件和程序是同一回事呢?事實(shí)上,如果你能將軟件和程序分開(kāi)來(lái)理解,那么你會(huì)發(fā)現(xiàn),為了應(yīng)對(duì)以上的情況,我們還有其他的解決辦法。
我國(guó)有一個(gè)很重要但是很麻煩的制度,那就是戶籍制度。它的本意是為了更好的管理人口事宜。每當(dāng)一個(gè)孩子出生,我們就需要在戶籍管理的地方去給他辦理戶籍入戶;而每當(dāng)一個(gè)人去世,我們也需要在相應(yīng)的地方銷去他的戶籍。既然我們可以視類為生命,那么我們能否通過(guò)學(xué)習(xí)這樣的戶籍管理制度來(lái)動(dòng)態(tài)地管理類呢?
事實(shí)上這樣的管理是可行的,而且Java虛擬機(jī)本身正是基于這樣的機(jī)制來(lái)運(yùn)行程序的。因此我們是否可以這樣來(lái)架構(gòu)軟件框架。首先,我們的軟件有一個(gè)配置文件,配置文件其實(shí)是一個(gè)文本,里面詳細(xì)描述了,我們的軟件核心部分運(yùn)行起來(lái)后還需要從什么路徑加載些什么類需要何時(shí)調(diào)用什么方法等。這樣當(dāng)我們需要加或減某些功能時(shí),我們只需要簡(jiǎn)單地修改配置文本文件,然后刪除或者添加相應(yīng)的.class文件就可以了。
如果你足夠敏感,你或許會(huì)發(fā)現(xiàn),這種方式形成的配置文件幾乎可以相當(dāng)于一門腳本語(yǔ)言了。而且這個(gè)腳本的解釋器也是我們自己寫的,另外關(guān)鍵是它是開(kāi)發(fā)的,你可以為它動(dòng)態(tài)地加入一些新的類以增加它的功能。
不要以為這僅僅是一個(gè)設(shè)想,雖然要開(kāi)發(fā)成一門完備的腳本語(yǔ)言確實(shí)比較麻煩。但是在一些網(wǎng)絡(luò)端的大型項(xiàng)目中,通過(guò)配置文件 + ClassLoader + 反射機(jī)制結(jié)合形成的這種軟件解耦和方式已經(jīng)用得比較普遍了。
所以,在此我不是在提出一種設(shè)想,而是在介紹業(yè)界處理此類問(wèn)題的一種解決方案。
反射安全
文章讀到這里,我想你應(yīng)該由衷地感嘆,Java反射機(jī)制實(shí)在是太強(qiáng)大了。但是,如果你有一些安全意識(shí)的話,就會(huì)發(fā)現(xiàn)Java這個(gè)機(jī)制強(qiáng)大得似乎有些過(guò)頭了。前面我們提到,Java反射甚至可以訪問(wèn)private方法和屬性。為了讓大家對(duì)Java反射有更全面的了解,樹(shù)立正確的人生觀價(jià)值觀,本小節(jié)將對(duì)Java的安全問(wèn)題做一個(gè)概要性的介紹。
相對(duì)于C++來(lái)說(shuō),Java算是比較安全的語(yǔ)言了。這與它們的運(yùn)行機(jī)制有密切的關(guān)系,C++運(yùn)行于本地,也就是說(shuō)幾乎所有程序的權(quán)限理論上都是相同的。而Java由于是運(yùn)行于虛擬機(jī)中,而不直接與外部聯(lián)系,所以實(shí)際上Java的運(yùn)行環(huán)境是一個(gè)“沙盒”環(huán)境。
Java的安全機(jī)制其實(shí)是比較復(fù)雜的,至少對(duì)于我來(lái)說(shuō)是如此。作為Java的安全模型,它包括了:字節(jié)碼驗(yàn)證器、類加載器、安全管理器、訪問(wèn)控制器等一系列的組件。之前文中提到過(guò),我把Android安全權(quán)限劃分為三個(gè)等級(jí):第一級(jí)是針對(duì)編譯期的“@hide”標(biāo)記;第二級(jí)是針對(duì)訪問(wèn)權(quán)限的private等修飾;第三級(jí)則是以安全管理器為托管的Permission機(jī)制。
Java反射確實(shí)可以訪問(wèn)private的方法和屬性,這是繞過(guò)第二級(jí)安全機(jī)制的方法(之一)。它其實(shí)是Java本身為了某種目的而留下的類似于“后門”的東西,或者說(shuō)是為了方便調(diào)試?不管如何,它的原理其實(shí)是關(guān)閉訪問(wèn)安全檢查。
如果你具有獨(dú)立鉆研的精神的話,你會(huì)發(fā)現(xiàn)之前我們提到的Field、Method和Constructor類,它們都有一個(gè)共同的父類AccessibleObject 。AccessibleObject 有一個(gè)公共方法:void setAccessible(boolean flag)。正是這個(gè)方法,讓我們可以改變動(dòng)態(tài)的打開(kāi)或者關(guān)閉訪問(wèn)安全檢查,從而訪問(wèn)到原本是private的方法或域。另外,訪問(wèn)安全檢查是一件比較耗時(shí)的操作,關(guān)閉它反射的性能也會(huì)有較大提升。
不要認(rèn)為我們繞過(guò)了前兩級(jí)安全機(jī)制就沾沾自喜了,因?yàn)檫@兩級(jí)安全并不是真正為了安全而設(shè)置的。它們的作用更多的是為了更好的完善規(guī)則。而第三級(jí)安全才是真正為了防止惡意攻擊而出現(xiàn)的。在這一級(jí)的防護(hù)下,你甚至可能都無(wú)法完成反射(ReflectPermission),其他的一切自然無(wú)從說(shuō)起。
對(duì)于這一級(jí),我的了解還太少,并且也與本文的主題相關(guān)甚少。以后有機(jī)會(huì),深入學(xué)習(xí)之后再聊吧!
最近在成都寫一個(gè)移動(dòng)增值項(xiàng)目,俺負(fù)責(zé)后臺(tái)server端。功能很簡(jiǎn)單,手機(jī)用戶通過(guò)GPRS打開(kāi)Socket與服務(wù)器連接,我則根據(jù)用戶傳過(guò)來(lái)的數(shù)據(jù)做出響應(yīng)。做過(guò)類似項(xiàng)目的兄弟一定都知道,首先需要定義一個(gè)類似于MSNP的通訊協(xié)議,不過(guò)今天的話題是如何把這個(gè)系統(tǒng)設(shè)計(jì)得具有高度的擴(kuò)展性。由于這個(gè)項(xiàng)目本身沒(méi)有進(jìn)行過(guò)較為完善的客戶溝通和需求分析,所以以后肯定會(huì)有很多功能上的擴(kuò)展,通訊協(xié)議肯定會(huì)越來(lái)越龐大,而我作為一個(gè)不那么勤快的人,當(dāng)然不想以后再去修改寫好的程序,所以這個(gè)項(xiàng)目是實(shí)踐面向?qū)ο笤O(shè)計(jì)的好機(jī)會(huì)。
首先定義一個(gè)接口來(lái)隔離類:
package org.bromon.reflect;
public interface Operator
{
public java.util.List act(java.util.List params)
}
根據(jù)設(shè)計(jì)模式的原理,我們可以為不同的功能編寫不同的類,每個(gè)類都繼承Operator接口,客戶端只需要針對(duì)Operator接口編程就可以避免很多麻煩。比如這個(gè)類:
package org.bromon.reflect.*;
public class Success implements Operator
{
public java.util.List act(java.util.List params)
{
List result=new ArrayList();
result.add(new String(“操作成功”));
return result;
}
}
我們還可以寫其他很多類,但是有個(gè)問(wèn)題,接口是無(wú)法實(shí)例化的,我們必須手動(dòng)控制具體實(shí)例化哪個(gè)類,這很不爽,如果能夠向應(yīng)用程序傳遞一個(gè)參數(shù),讓自己去選擇實(shí)例化一個(gè)類,執(zhí)行它的act方法,那我們的工作就輕松多了。
很幸運(yùn),我使用的是Java,只有Java才提供這樣的反射機(jī)制,或者說(shuō)內(nèi)省機(jī)制,可以實(shí)現(xiàn)我們的無(wú)理要求。編寫一個(gè)配置文件emp.properties:
#成功響應(yīng)
1000=Success
#向客戶發(fā)送普通文本消息
2000=Load
#客戶向服務(wù)器發(fā)送普通文本消息
3000=Store
文件中的鍵名是客戶將發(fā)給我的消息頭,客戶發(fā)送1000給我,那么我就執(zhí)行Success類的act方法,類似的如果發(fā)送2000給我,那就執(zhí)行Load類的act方法,這樣一來(lái)系統(tǒng)就完全符合開(kāi)閉原則了,如果要添加新的功能,完全不需要修改已有代碼,只需要在配置文件中添加對(duì)應(yīng)規(guī)則,然后編寫新的類,實(shí)現(xiàn)act方法就ok,即使我棄這個(gè)項(xiàng)目而去,它將來(lái)也可以很好的擴(kuò)展。這樣的系統(tǒng)具備了非常良好的擴(kuò)展性和可插入性。
下面這個(gè)例子體現(xiàn)了動(dòng)態(tài)加載的功能,程序在執(zhí)行過(guò)程中才知道應(yīng)該實(shí)例化哪個(gè)類:
package org.bromon.reflect.*;
import java.lang.reflect.*;
public class TestReflect
{
//加載配置文件,查詢消息頭對(duì)應(yīng)的類名
private String loadProtocal(String header)
{
String result=null;
try
{
Properties prop=new Properties();
FileInputStream fis=new FileInputStream("emp.properties");
prop.load(fis);
result=prop.getProperty(header);
fis.close();
}catch(Exception e)
{
System.out.println(e);
}
return result;
}
//針對(duì)消息作出響應(yīng),利用反射導(dǎo)入對(duì)應(yīng)的類
public String response(String header,String content)
{
String result=null;
String s=null;
try
{
/*
* 導(dǎo)入屬性文件emp.properties,查詢header所對(duì)應(yīng)的類的名字
* 通過(guò)反射機(jī)制動(dòng)態(tài)加載匹配的類,所有的類都被Operator接口隔離
* 可以通過(guò)修改屬性文件、添加新的類(繼承MsgOperator接口)來(lái)擴(kuò)展協(xié)議
*/
s="org.bromon.reflect."+this.loadProtocal(header);
//加載類
Class c=Class.forName(s);
//創(chuàng)建類的事例
Operator mo=(Operator)c.newInstance();
//構(gòu)造參數(shù)列表
Class params[]=new Class[1];
params[0]=Class.forName("java.util.List");
//查詢act方法
Method m=c.getMethod("act",params);
Object args[]=new Object[1];
args[0]=content;
//調(diào)用方法并且獲得返回
Object returnObject=m.invoke(mo,args);
}catch(Exception e)
{
System.out.println("Handler-response:"+e);
}
return result;
}
public static void main(String args[])
{
TestReflect tr=new TestReflect();
tr.response(args[0],”消息內(nèi)容”);
}
}
posted on 2011-09-23 17:14 順其自然EVO 閱讀(677) 評(píng)論(0) 編輯 收藏