java swing jbutton_Java 反射
點(diǎn)擊上方“凌天實(shí)驗(yàn)室”,“星標(biāo)或置頂公眾號(hào)”
漏洞、技術(shù)還是其他,我都想第一時(shí)間和你分享
1前? 言
本章為新手向零基礎(chǔ) Java 反射學(xué)習(xí)筆記。
截取部分本實(shí)驗(yàn)室發(fā)起的項(xiàng)目javaweb-sec。
Gitbook地址:https://javasec.org/
2反射的基本定義
官方文檔:https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html
Reflection(反射)賦予了 Java 代碼從已加載的類中發(fā)現(xiàn)成員變量(Fields)、成員方法(Methods)和構(gòu)造方法(Constructors)的能力,并可以在安全限制內(nèi)使用這些反射得到的成員變量、成員方法、構(gòu)造方法來操作他們對(duì)應(yīng)的底層對(duì)象。
簡(jiǎn)而言之,你可以在運(yùn)行狀態(tài)中通過反射機(jī)制做到:
對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;
對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性。
這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制。
不得不說,反射是一種十分具有 "Hacker" 精神的機(jī)制。
3反射的基礎(chǔ)
從源代碼到程序運(yùn)行,大致經(jīng)歷的步驟如下:
創(chuàng)建源文件后,程序會(huì)被編譯器編譯為后綴名為?.class?的文件;
通過類加載器系統(tǒng)將字節(jié)碼.class加載入 JVM 的內(nèi)存中,類的加載是通過 ClassLoader 及其子類來完成的;在加載階段,虛擬機(jī)要完成三件事情:通過一個(gè)類的全限定名來獲取其定義的二進(jìn)制字節(jié)流,將這個(gè)字節(jié)流所代表的的靜態(tài)儲(chǔ)存結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在 Java 堆中生成一個(gè)代表這個(gè)類的?java.lang.Class?對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口。在加載的過程中將使用雙親委派模型進(jìn)行類的加載;
對(duì)字節(jié)碼進(jìn)行驗(yàn)證;
解析類、接口、字段,是虛擬機(jī)將常量池中的符號(hào)引用轉(zhuǎn)化為直接引用的過程;
類初始化,這里需要注意的是,在使用?Java.lang.reflect?包的方法對(duì)類進(jìn)行反射調(diào)用時(shí),如果類還沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化;
執(zhí)行。
可以看到,在加載的過程中,當(dāng)一個(gè) class 被加載,或當(dāng)加載器(classloader)的?defineClass()被 JVM 調(diào)用時(shí),JVM 將自動(dòng)產(chǎn)生一個(gè) Class 對(duì)象,并且這個(gè)對(duì)象會(huì)保存在同名的?.class?文件里,當(dāng)我們 new 一個(gè)新對(duì)象或者引用一個(gè)靜態(tài)成員變量時(shí),JVM 中的加載器系統(tǒng)會(huì)將對(duì)應(yīng)的 Class 對(duì)象加載到 JVM 中,然后 JVM 再根據(jù)這個(gè)類型信息相關(guān)的 Class 對(duì)象創(chuàng)建我們需要實(shí)例對(duì)象或者提供靜態(tài)變量的引用值。
因此,這些在程序加載過程中產(chǎn)生的類的 Class 對(duì)象就是反射的基礎(chǔ)。
Class 類位于包?java.lang?下:
public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement可以看到,這是個(gè) final 類,實(shí)現(xiàn)了 4 個(gè)接口。
這個(gè)類十分特殊,它同樣的繼承自 Object ,它的實(shí)例能用來表達(dá) Java 程序運(yùn)行時(shí)的 classes 和 interfaces,也能用來表達(dá) enum、array、Java 基礎(chǔ)類型(boolean、byte、char、short、int、long、double、float)以及關(guān)鍵詞 void。
Class 沒有公共構(gòu)造方法。Class 對(duì)象是在加載類時(shí)由 Java 虛擬機(jī)以及通過調(diào)用類加載器中的?defineClass?方法自動(dòng)構(gòu)造的。
4獲取 Class 對(duì)象的方法
通過上面的了解,我們知道,如果想使用反射,必須得獲得 Class 對(duì)象。
除了?java.lang.reflect.ReflectPermission?以外,java.lang.reflect?中的其他類都沒有 public 的構(gòu)造函數(shù),也就是說要得到這些類,我們必須通過 Class 。
下面列舉了能夠獲取 Class 對(duì)象的方法:
1. Object.getClass()
第一種方法是通過類的實(shí)例來獲取對(duì)應(yīng)的 Class 對(duì)象。
byte[] bytes = new byte[1024];Class> c = bytes.getClass();
但是對(duì)于基礎(chǔ)數(shù)據(jù)類型不能使用這種方式。
2. 使用.Class 語法
通過類的類型獲取 Class 對(duì)象,基本類型同樣可以使用這種方法。
Class> c = boolean.class;Class> c = String.class;
3. Class.forName()
通過類的全限定名獲取Class對(duì)象, 基本類型無法使用此方法。
try {Class> c = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
對(duì)于數(shù)組比較特殊:
Class> doubleArray = Class.forName("[D"); //相當(dāng)于double[].classClass> cStringArray = Class.forName("[[Ljava.lang.String;"); //相當(dāng)于String[][].class
如果在調(diào)用Class.forName()方法時(shí),沒有在編譯路徑下(classpath)找到對(duì)應(yīng)的類,那么將會(huì)拋出ClassNotFoundException。
4. 基礎(chǔ)類型封裝類型 TYPE
基本類型和 void 類型的包裝類可以使用 TYPE 字段獲取。
Class c = Double.TYPE; //等價(jià)于 double.classClass c = Void.TYPE;
5. 能夠返回 Class 類型的方法
另外還有一些反射方法可以獲取 Class 對(duì)象,但前提是你已經(jīng)獲取了一個(gè) Class 對(duì)象。比如說你已經(jīng)獲取了一個(gè)類的 Class 對(duì)象,就可以通過Class.getSuperclass()方法獲取這個(gè)類的父類的 Class 對(duì)象。
Class> c = javax.swing.JButton.class.getSuperclass();類似能夠返回 Class 對(duì)象的方法還有:
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
還有部分類能夠返回 Class 對(duì)象,但基本上都是調(diào)用?Class.forName(),此處不再列舉。
5從 Class 中獲取信息
在獲取了 Class 對(duì)象后,就可以通過 Class 類提供的方法來獲取其中的信息和進(jìn)行操作了。
1. 獲取類的信息
| 類名 | String getName() |
| 類名簡(jiǎn)稱 | String getSimpleName() |
| 規(guī)范化類名 | String getCanonicalName() |
| 類加載器 | ClassLoader getClassLoader() |
| 泛型的參數(shù)類型 | TypeVariable>[] getTypeParameters() |
| 直接繼承的父類 | Class super T> getSuperclass() |
| 直接繼承的父類(包含泛型信息) | Type getGenericSuperclass() |
| 包含的方法 | Method getMethod(String name, Class>... parameterTypes) |
| 內(nèi)部類 | Class>[] getDeclaredClasses() |
| 構(gòu)造器 | Constructor getConstructor(Class>... parameterTypes) |
| 包含的屬性 | Field getField(String name) |
| 修飾符 | int getModifiers() |
| 類的標(biāo)記 | Object[] getSigners() |
| 數(shù)組的 Class 對(duì)象 | Class> getComponentType() |
| 所在包 | Package getPackage() |
| 所實(shí)現(xiàn)的接口 | Class>[] getInterfaces() |
| 所實(shí)現(xiàn)的接口(包含泛型信息) | Type[] getGenericInterfaces() |
| 包含的Annotation | A getAnnotation(Class annotationClass) |
在 Class 類中,可以看到類似 getEnclosing*、getDeclared* 與 getDeclaringClass,可以理解為在不同”域“中獲取信息,由于篇幅的原因不再進(jìn)行羅列:
get*:返回當(dāng)前類和繼承層次中的所有父類的成員
getEnclosing*:返回內(nèi)部或匿名的封閉成員
getDeclared*:返回當(dāng)前類中的成員(不包含父類)
getDeclaringClass:返回當(dāng)前類聲明所在的類
2. 判斷類本身信息的方法
Class 類提供了一些判斷類本身信息的方法,通常命名為 isXxxxx ,返回類型均為 boolean。
| 是否為注解類型 | boolean isAnnotation() |
| 是否使用了該Annotation修飾 | boolean isAnnotationPresent(Class extends Annotation> annotationClass) |
| 是否為匿名類 | boolean isAnonymousClass() |
| 是否為數(shù)組類型 | boolean isArray() |
| 是否為枚舉類型 | boolean isEnum() |
| 判斷兩個(gè)類的是否關(guān)聯(lián) | boolean isAssignableFrom() |
| 是否為接口 | boolean isInterface() |
| obj 是否是該 Class 的實(shí)例 | boolean isInstance(Object obj) |
| 該類是否為局部類 | boolean isLocalClass() |
| 該類是否為成員類 | boolean isMemberClass() |
| 是否為基礎(chǔ)類型 | boolean isPrimitive() |
| 是否由Java編譯器引入 | boolean isSynthetic() |
3. 獲取構(gòu)造方法
Class 類提供了四個(gè) public 方法,用于獲取某個(gè)類的構(gòu)造方法。
| Constructor getConstructor(Class[] params) | 根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個(gè)具體的具有public屬性的構(gòu)造函數(shù) |
| Constructor getConstructors() | 返回所有具有public屬性的構(gòu)造函數(shù)數(shù)組 |
| Constructor getDeclaredConstructor(Class[] params) | 根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個(gè)具體的構(gòu)造函數(shù)(不分public和非public屬性) |
| Constructor getDeclaredConstructors() | 返回該類中所有的構(gòu)造函數(shù)數(shù)組(不分public和非public屬性) |
如果想要反射出無參數(shù)的構(gòu)造方法,可以直接使用?newInstanse()?方法創(chuàng)建新實(shí)例,因?yàn)樵摲椒ǖ谋举|(zhì)即調(diào)用類的無參數(shù)構(gòu)造方法。
4. 獲取類的成員方法
| Method getMethod(String name, Class[] params) | 根據(jù)方法名和參數(shù),返回一個(gè)具體的具有public屬性的方法 |
| Method[] getMethods() | 返回所有具有public屬性的方法數(shù)組 |
| Method getDeclaredMethod(String name, Class[] params) | 根據(jù)方法名和參數(shù),返回一個(gè)具體的方法(不分public和非public屬性) |
| Method[] getDeclaredMethods() | 返回該類中的所有的方法數(shù)組(不分public和非public屬性) |
5. 獲取類的成員屬性
| Field getField(String name) | 根據(jù)變量名,返回一個(gè)具體的具有public屬性的成員變量 |
| Field[] getFields() | 返回具有public屬性的成員變量的數(shù)組 |
Field getDeclaredField(String name) | 根據(jù)變量名,返回一個(gè)成員變量(不分public和非public屬性) |
| Field[] getDelcaredFields() | 返回所有成員變量組成的數(shù)組(不分public和非public屬性) |
上面列舉的部分方法中還存在部分方法的重載方法,不再贅述,按需使用。
6反射操作
能夠反射得到類、屬性、方法之后,應(yīng)該如何操作呢?
首先我們先看反射的包?java.lang.reflect?下的 Member 接口,顧名思義,這是一個(gè)標(biāo)識(shí)為成員的接口,這個(gè)接口有若干個(gè)實(shí)現(xiàn):
其中我們首先關(guān)注的是:
java.lang.reflect.Field?:對(duì)應(yīng)類變量。
java.lang.reflect.Method?:對(duì)應(yīng)類方法。
java.lang.reflect.Constructor?:對(duì)應(yīng)類構(gòu)造函數(shù)。
反射就是通過這三個(gè)類才能在運(yùn)行時(shí)改變對(duì)象狀態(tài)。
而 Class 對(duì)象的?getXXX()?方法返回的成員方法,成員屬性,構(gòu)造方法就是 reflect 包中相對(duì)應(yīng)的類。
其中 Method 類和 Constructor 類繼承自 Executable 類,Executable 類實(shí)現(xiàn)了 Member 接口,而 Field 類則直接實(shí)現(xiàn)了Member 接口。
從 JDK 1.8 開始,java.lang.reflect.Executable.getParameters?為我們提供了獲取普通方法或者構(gòu)造方法的名稱的能力,因此 Method 類和 Constructor 類也具有這個(gè)能力。
1. Field
每個(gè)成員變量有類型和值。java.lang.reflect.Field?為我們提供了獲取當(dāng)前對(duì)象的成員變量的類型,和重新設(shè)值的方法。
具體方法如下:
獲取變量的類型:
| Class> getType() | 返回這個(gè)變量的類型 |
| Type getGenericType() | 如果當(dāng)前屬性有簽名屬性就返回,否則返回getType() |
這里再復(fù)習(xí)一下類型,類中的變量分為兩種類型:基本類型和引用類型:
基本類型( 8 種)
整數(shù):byte, short, int, long
浮點(diǎn)數(shù):float, double
字符:char
布爾值:boolean
引用類型
類,枚舉,數(shù)組,接口都是引用類型
java.io.Serializable 接口,基本類型的包裝類(比如?java.lang.Double)也是引用類型
獲取和修改成員變量的值:
拿到一個(gè)對(duì)象后,我們可以在運(yùn)行時(shí)修改它的成員變量的值,對(duì)運(yùn)行時(shí)來說,反射修改變量值的操作和類中修改變量的結(jié)果是一樣的。
基本類型的獲取方法:
| byte getByte(Object obj) | 獲取一個(gè)靜態(tài)或?qū)嵗?byte 字段的值 |
| int getInt(Object obj) | 獲取 int 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 int 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
| short getShort(Object obj) | 獲取 short 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 short 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
| long getLong(Object obj) | 獲取 long 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 long 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
| float getFloat(Object obj) | 獲取 float 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 float 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
| double getDouble(Object obj) | 獲取 double 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 double 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
| boolean getBoolean(Object obj) | 獲取一個(gè)靜態(tài)或?qū)嵗?boolean 字段的值 |
| char getChar(Object obj) | 獲取 char 類型或另一個(gè)通過擴(kuò)展轉(zhuǎn)換可以轉(zhuǎn)換為 char 類型的基本類型的靜態(tài)或?qū)嵗侄蔚闹?/td> |
基本類型的設(shè)置方法:
| void setByte(Object obj, byte b) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) byte 值 |
| void setShort(Object obj, short s) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) short 值 |
| void setInt(Object obj, int i) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) int 值 |
| void setLong(Object obj, long l) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) long 值 |
| void setFloat(Object obj, float f) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) float 值 |
| void setDouble(Object obj, double d) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) double 值 |
| void setBoolean(Object obj, boolean z) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) boolean 值 |
| void setChar(Object obj, char c) | 將字段的值設(shè)置為指定對(duì)象上的一個(gè) char 值 |
引用類型的獲取和設(shè)置方法:
| Object get(Object obj) | 返回指定對(duì)象上此 Field 表示的字段的值 |
| void set(Object obj, Object value) | 將指定對(duì)象變量上此 Field 對(duì)象表示的字段設(shè)置為指定的新值 |
2. Method
繼承的方法(包括重載、重寫和隱藏的)會(huì)被編譯器強(qiáng)制執(zhí)行,這些方法都無法反射。因此,反射一個(gè)類的方法時(shí)不考慮父類的方法,只考慮當(dāng)前類的方法。
每個(gè)方法都由修飾符、返回值、參數(shù)、注解和拋出的異常組成。
java.lang.reflect.Method?方法為我們提供了獲取上述部分的 API。
重寫 Object 類的方法不再描述,重寫 Executable 的方法將在后面部分描述,在此只列舉一些 Method 類自己的方法。
| Object getDefaultValue() | 返回由此方法實(shí)例表示的注釋成員的默認(rèn)值 |
| Type getGenericReturnType() | 獲取目標(biāo)方法返回類型對(duì)應(yīng)的 Type 對(duì)象 |
| Class> getReturnType() | 獲取目標(biāo)方法返回類型對(duì)應(yīng)的 Class 對(duì)象 |
| boolean isBridge() | 判斷是否是橋接方法 |
| boolean isDefault() | 如果此方法是默認(rèn)方法,則返回 true ; 否則返回 false |
| Object invoke(Object obj, Object... args) | 使用反射執(zhí)行方法 |
首先來看一下?getReturnType()?和?getGenericReturnType()?的異同。
getReturnType()返回類型為 Class,getGenericReturnType()返回類型為 Type ; Class 實(shí)現(xiàn) Type 。
返回值為普通簡(jiǎn)單類型如 Object , int , String 等,getGenericReturnType()返回值和getReturnType()一樣。
例如?public String function1()那么各自返回值為:getReturnType() : class java.lang.StringgetGenericReturnType() : class java.lang.String
返回值為泛型
例如public T function2()那么各自返回值為:getReturnType() : class java.lang.ObjectgetGenericReturnType() : T
返回值為參數(shù)化類型
例如public Class function3()那么各自返回值為:getReturnType() : class java.lang.ClassgetGenericReturnType() : java.lang.Class
其實(shí)反射中所有形如getGenericXXX()的方法規(guī)則都與上面所述類似。
然后就是非常重要的invoke()方法,
public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException
第一個(gè) Object 參數(shù)代表的是對(duì)應(yīng)的 Class 對(duì)象實(shí)例,第二個(gè)參數(shù)是可變形參,能夠接受多個(gè)參數(shù)。
這里需要注意的是,invoke 方法的兩個(gè)參數(shù)均為?Object?類型。
簡(jiǎn)單來說,通過?invoke()?方法可以讓我們調(diào)用反射得到的類的方法。那么具體是怎么實(shí)現(xiàn)的呢?
invoke過程圖解:
此處不進(jìn)行過多描述,有興趣可以跟一下源碼。
3. Constructor
同樣地,我們也只列舉 Constructor 自己的 public 方法:
| T newInstance(Object ... initargs) | 調(diào)用構(gòu)造方法創(chuàng)建新實(shí)例 |
方法使用此 Constructor 對(duì)象表示的構(gòu)造函數(shù),使用指定的初始化參數(shù)來創(chuàng)建和初始化構(gòu)造函數(shù)的聲明類的新實(shí)例。個(gè)別參數(shù)自動(dòng)展開以匹配原始形式參數(shù),原始參考參數(shù)和參考參數(shù)都需要進(jìn)行方法調(diào)用轉(zhuǎn)換。
通過 Class 對(duì)象也可以創(chuàng)建新實(shí)例,但是兩者的異同在于:
Class.newInstance()只能反射無參數(shù)的構(gòu)造器,也就是使用無參數(shù)構(gòu)造方法創(chuàng)建新實(shí)例,而?Constructor.newInstance()可以反射任何構(gòu)造器;
Class.newInstance()?需要構(gòu)造器可見(visible),Constructor.newInstance()可以反射私有構(gòu)造器;
Class.newInstance()對(duì)于捕獲或者未捕獲的異常均由構(gòu)造器拋出,Constructor.newInstance()通常會(huì)把拋出的異常封裝成InvocationTargetException拋出;
因此,還是建議直接使用?Constructor.newInstance()?反射構(gòu)造方法,幾乎全部的框架都是使用此種模式進(jìn)行反射的。
4. Executable
Executable 抽象類繼承 AccessibleObject 類,實(shí)現(xiàn)了 Member 接口,并有兩個(gè)子類分別為 Constructor 和 Method,如下:
該類中定義了多個(gè)方法,能夠在通過反射獲得的成員方法或構(gòu)造方法中使用:
該抽象類聲明的方法有:
| Class>[] getParameterTypes() | 按照聲明順序返回 Class 對(duì)象的數(shù)組,這些對(duì)象描述了此 Method/Constructor 對(duì)象所表示的方法的形參類型。 |
| Class>[] getExceptionTypes() | 返回 Class 對(duì)象的數(shù)組,這些對(duì)象描述了聲明將此 Method/Constructor 對(duì)象表示的底層方法拋出的異常類型。 |
| Type[] getGenericParameterTypes() | 按照聲明順序返回 Type 對(duì)象的數(shù)組,這些對(duì)象描述了此 Method/Constructor 對(duì)象所表示的形參類型的。 |
| Type[] getGenericExceptionTypes() | 返回 Type 對(duì)象數(shù)組,這些對(duì)象描述了聲明由此 Method/Constructor 對(duì)象拋出的異常的類型。 |
| String toGenericString() | 返回描述此 Method/Constructor 的字符串,包括類型參數(shù)。 |
| Annotation[][] getParameterAnnotations() | 返回表示按照聲明順序?qū)Υ?Method/Constructor 對(duì)象所表示方法的形參進(jìn)行注釋的那個(gè)數(shù)組的數(shù)組。 |
| AnnotatedType getAnnotatedReturnType() | 返回一個(gè)AnnotatedType對(duì)象,該對(duì)象表示使用一個(gè)類型來指定由該可執(zhí)行文件表示的方法/構(gòu)造函數(shù)的返回類型 |
| Type[] getGenericExceptionTypes() | 返回一個(gè)AnnotatedType對(duì)象數(shù)組,這些對(duì)象表示使用類型來指定由該可執(zhí)行文件表示的方法/構(gòu)造函數(shù)聲明的異常 |
| AnnotatedType getAnnotatedReceiverType() | 返回一個(gè)AnnotatedType對(duì)象,該對(duì)象表示使用一個(gè)類型來指定該可執(zhí)行對(duì)象表示的方法/構(gòu)造函數(shù)的接收者類型 |
| AnnotatedType[] getAnnotatedParameterTypes() | 返回一個(gè)AnnotatedType對(duì)象數(shù)組,這些對(duì)象表示使用類型來指定由該可執(zhí)行文件表示的方法/構(gòu)造函數(shù)的形式參數(shù)類型 |
| int getParameterCount() | 獲取參數(shù)的個(gè)數(shù)(無論是顯式聲明的還是隱式聲明的或不聲明的 |
| Parameter[] getParameters() | 返回一個(gè)參數(shù)對(duì)象數(shù)組,該數(shù)組表示該方法對(duì)象的所有參數(shù) |
| boolean isVarArgs() | 是否是可變參數(shù) |
5. AccessibleObject
在?java.lang.reflect?包中,存在一個(gè) AccessibleObject 類,細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了,該類是Method、Field、Constructor 類的基類,它提供了標(biāo)記反射對(duì)象的能力,以抑制在使用時(shí)使用默認(rèn) Java 語言訪問控制檢查,從而能夠任意調(diào)用被私有化保護(hù)的方法、域和構(gòu)造函數(shù)。
此類實(shí)現(xiàn)了 AnnotatedElement 接口,主要是注解相關(guān)的操作。
聲明的方法:
| boolean isAccessible() | 獲取此對(duì)象的accessible標(biāo)志的值(布爾類型) |
| void setAccessible(AccessibleObject[] array, boolean flag) | 使用單一安全檢查來設(shè)置對(duì)象數(shù)組的可訪問標(biāo)志的一個(gè)方便的方法(為了效率),靜態(tài)方法 |
| void setAccessible(boolean flag) | 將對(duì)象的可訪問標(biāo)志設(shè)置為指示的布爾值 |
通過調(diào)用?setAccessible()?方法會(huì)關(guān)閉反射訪問檢查,這行代碼執(zhí)行之后不論是私有的、受保護(hù)的以及包訪問的作用域,你都可以在任何地方訪問,即使你不在他的訪問權(quán)限作用域之內(nèi)。
當(dāng)isAccessible()的結(jié)果是 false 時(shí)不允許通過反射訪問該字段;
當(dāng)該字段時(shí) private 修飾時(shí)isAccessible()得到的值是 false ,必須要改成 true 才可以訪問;
所以?f.setAccessible(true)得作用就是讓我們?cè)谟梅瓷鋾r(shí)訪問私有變量。
所以你其實(shí)并不是把 private 方法改成了 public ,也沒有更改任何的權(quán)限,你只是關(guān)閉了反射訪問的檢查,這種情況下,其實(shí)可以導(dǎo)致一些效率上的提升,與此同時(shí)帶來的就是安全性的下降。
6. AnnotatedElement
此接口位于包?java.lang.reflect?下。
這個(gè)接口的對(duì)象代表了在當(dāng)前 JVM 中的一個(gè)“被注解元素”。在 Java 語言中,所有實(shí)現(xiàn)了這個(gè)接口的“元素”都是可以“被注解的元素”。
使用這個(gè)接口中聲明的方法可以讀取(通過 Java 的反射機(jī)制)“被注解元素”的注解。
這個(gè)接口中的所有方法返回的注解都是不可變的、并且都是可序列化的。這個(gè)接口中所有方法返回的數(shù)組可以被調(diào)用者修改,而不會(huì)影響其返回給其他調(diào)用者的數(shù)組。
此類具有以下的實(shí)現(xiàn)類:
AccessibleObject(可訪問對(duì)象,如:方法、構(gòu)造器、屬性等)
Class
Constructor
Executable(可執(zhí)行的,如構(gòu)造器和方法)
Field(屬性,類中屬性的類型)
Method(方法,類中方法的類型)
Package(包)
Parameter(參數(shù),主要指方法或函數(shù)的參數(shù),其實(shí)是這些參數(shù)的類型)
接口聲明的方法有:
| boolean isAnnotationPresent(Class extends Annotation> annotationClass) | 如果指定類型的注解出現(xiàn)在當(dāng)前元素上,則返回 true ,否則將返回false。這種方法主要是為了方便地訪問一些已知的注解。 |
| T getAnnotation(Class annotationClass) | 如果在當(dāng)前元素上存在參數(shù)所指定類型(annotationClass)的注解,則返回對(duì)應(yīng)的注解,否則將返回 null 。 |
| Annotation[] getAnnotations() | 返回在這個(gè)元素上的所有注解。如果該元素沒有注釋,則返回值是長(zhǎng)度為0的數(shù)組。該方法的調(diào)用者可以自由地修改返回的數(shù)組;它不會(huì)對(duì)返回給其他調(diào)用者的數(shù)組產(chǎn)生影響。 |
| T[] getAnnotationsByType(Class annotationClass) | 返回與該元素相關(guān)聯(lián)的注解。如果沒有與此元素相關(guān)聯(lián)的注解,則返回值是長(zhǎng)度為0的數(shù)組。這個(gè)方法與getAnnotation(Class)的區(qū)別在于,該方法檢測(cè)其參數(shù)是否為可重復(fù)的注解類型( JLS 9.6 ),如果是,則嘗試通過“ looking through ”容器注解來查找該類型的一個(gè)或多個(gè)注解。該方法的調(diào)用者可以自由地修改返回的數(shù)組;它不會(huì)對(duì)返回給其他調(diào)用者的數(shù)組產(chǎn)生影響。 |
| T getDeclaredAnnotation(Class annotationClass) | 如果參數(shù)中所指定類型的注解是直接存在于當(dāng)前元素上的,則返回對(duì)應(yīng)的注解,否則將返回null。這個(gè)方法忽略了繼承的注解。(如果沒有直接在此元素上顯示注釋,則返回null。) |
| T[] getDeclaredAnnotationsByType(Class annotationClass) | 如果參數(shù)中所指定類型的注解是直接存在或間接存在于當(dāng)前元素上的,則返回對(duì)應(yīng)的注解。這種方法忽略了繼承的注解。如果沒有直接或間接地存在于此元素上的指定注解,則返回值是長(zhǎng)度為0的數(shù)組。這個(gè)方法和getDeclaredAnnotation(Class)的區(qū)別在于,這個(gè)方法檢測(cè)它的參數(shù)是否為可重復(fù)的注釋類型( JLS 9.6 ),如果是,則嘗試通過“ looking through ”容器注解來查找該類型的一個(gè)或多個(gè)注解。該方法的調(diào)用者可以自由地修改返回的數(shù)組;它不會(huì)對(duì)返回給其他調(diào)用者的數(shù)組產(chǎn)生影響。 |
| Annotation[] getDeclaredAnnotations() | 返回直接出現(xiàn)在這個(gè)元素上的注解。這種方法忽略了繼承的注解。如果在此元素上沒有直接存在的注解,則返回值是長(zhǎng)度為0的數(shù)組。該方法的調(diào)用者可以自由地修改返回的數(shù)組;它不會(huì)對(duì)返回給其他調(diào)用者的數(shù)組產(chǎn)生影響。 |
7. Member
Member 接口標(biāo)識(shí)類或接口的所有公共成員的集合,包括繼承的成員。Constructor / Filed / Method 類都直接或間接的繼承此類。
Member 接口中定義的方法為:
| Class> getDeclaringClass() | 返回表示該類或接口的 Class 對(duì)象 |
| String getName() | 返回此成員表示的基礎(chǔ)成員或構(gòu)造函數(shù)的簡(jiǎn)單名稱 |
| int getModifiers() | 以整數(shù)形式返回此成員表示的成員或構(gòu)造函數(shù)的 Java 語言修飾符 |
| boolean isSynthetic() | 是否由 Java 編譯器引入 |
8. 反射操作總結(jié)
通過以上知識(shí)的學(xué)習(xí),可以知道:
AnnotatedElement?接口提供獲取注解相關(guān)能力。
Member?接口提供通用成員屬性獲取能力。
GenericDeclaration?接口提供給 Class 獲取泛型類型的能力。
Executable?抽象類提供獲取可執(zhí)行對(duì)象相關(guān)信息的能力。
AccessibleObject?抽象類提供判斷可更改可訪問標(biāo)識(shí)的能力。
Constructor、Method、Field各自基礎(chǔ)或?qū)崿F(xiàn)上述接口和抽象類,賦予了他們獲取相關(guān)信息的能力。
以上類或接口之間的關(guān)系為:
9. 數(shù)組和枚舉
數(shù)組和枚舉也是對(duì)象,但是在反射中,對(duì)數(shù)組和枚舉的創(chuàng)建、訪問和普通對(duì)象有些不同,所以 Java 反射為數(shù)組和枚舉提供了一些特定的API接口。
數(shù)組
數(shù)組類型
數(shù)組類型:數(shù)組本質(zhì)是一個(gè)對(duì)象,所以它也有自己的類型。例如對(duì)于int[] intArray,數(shù)組類型為class [I。數(shù)組類型中的[個(gè)數(shù)代表數(shù)組的維度,例如[代表一維數(shù)組,[[代表二維數(shù)組;[后面的字母代表數(shù)組元素類型,I代表int,一般為類型的首字母大寫( long 類型例外,為 J )。
Class> c = field.getType();
//判斷該變量是否為數(shù)組
if (c.isArray()) {
//獲取數(shù)組的元素類型
c.getComponentType()
}
創(chuàng)建和初始化數(shù)組
Java反射為我們提供了java.lang.reflect.Array類用來創(chuàng)建和初始化數(shù)組。
//創(chuàng)建數(shù)組, 參數(shù)componentType為數(shù)組元素的類型,后面不定項(xiàng)參數(shù)的個(gè)數(shù)代表數(shù)組的維度,參數(shù)值為數(shù)組長(zhǎng)度Array.newInstance(Class> componentType, int... dimensions)
//設(shè)置數(shù)組值,array為數(shù)組對(duì)象,index為數(shù)組的下標(biāo),value為需要設(shè)置的值
Array.set(Object array, int index, int value)
//獲取數(shù)組的值,array為數(shù)組對(duì)象,index為數(shù)組的下標(biāo)
Array.get(Object array, int index)
例子,用反射創(chuàng)建?int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2);Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);
注意:反射支持對(duì)數(shù)據(jù)自動(dòng)加寬,但不允許數(shù)據(jù)narrowing(變窄?真難翻譯)。意思是對(duì)于上述set方法,你可以在int類型數(shù)組中 set short類型數(shù)據(jù),但不可以set long類型數(shù)據(jù),否則會(huì)報(bào)IllegalArgumentException。
多維數(shù)組
Java反射沒有提供能夠直接訪問多維數(shù)組元素的API,但你可以把多維數(shù)組當(dāng)成數(shù)組的數(shù)組處理。
Object matrix = Array.newInstance(int.class, 2, 2);Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
或者
Object matrix = Array.newInstance(int.class, 2);Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
枚舉
枚舉隱式繼承自java.lang.Enum,Enum繼承自O(shè)bject,所以枚舉本質(zhì)也是一個(gè)類,也可以有成員變量,構(gòu)造方法,方法等;對(duì)于普通類所能使用的反射方法,枚舉都能使用;另外java反射額外提供了幾個(gè)方法為枚舉服務(wù)。
| Class.isEnum() | 此類是否為枚舉類型 |
| Class.getEnumConstants() | 返回按照聲明順序索引由枚舉類型定義的枚舉常量的列表 |
| java.lang.reflect.Field.isEnumConstant() | 此字段是否表示枚舉類型的元素 |
10. 常見異常
NoSuchMethodException
造成這種異常的可能有:
方法本身不存在
傳入的參數(shù)類型不匹配(也可能是泛型擦除導(dǎo)致)
傳入的參數(shù)個(gè)數(shù)不匹配
反射調(diào)用泛型方法時(shí),由于運(yùn)行前編譯器已經(jīng)把泛型擦除,參數(shù)類型會(huì)被擦除為上邊界(默認(rèn) Object)。
此時(shí)再試圖傳入特定類型的參數(shù)會(huì)導(dǎo)致NoSuchMethodException異常。
解決的方式是使用 Object 作為參數(shù)類型即可。
IllegalAccessException
當(dāng)你訪問 private 的方法或者 private 的類中的方法,會(huì)拋出IllegalAccessException異常。
也有可能是試圖操作 final 修飾的 Field 導(dǎo)致。
解決方法就是給該 method 設(shè)置?setAccessible(true)。
llegalArgumentException
如果一個(gè)方法沒有參數(shù),但是我們反射時(shí)傳入?yún)?shù),就會(huì)導(dǎo)致?llegalArgumentException。
InvocationTargetException
被調(diào)用的方法本身所拋出的異常在反射中都會(huì)以?InvocationTargetException拋出。
換句話說,反射調(diào)用過程中如果異常?InvocationTargetException拋出,說明反射調(diào)用本身是成功的,因?yàn)檫@個(gè)異常是目標(biāo)方法本身所拋出的異常。
通過調(diào)用?InvocationTargetException?對(duì)象的?getCause()?方法,會(huì)得到 Throwable 對(duì)象,原始的異常就包含在里面。
ClassNotFoundException
Class.forName()傳入的包名有誤,或 Class 本身不存在。將會(huì)引發(fā)此異常。
NoSuchFieldException
Field 名稱不正確,或?qū)?getField()?和?getDeclaredField()?的使用不正確將會(huì)引發(fā)此異常。
InstantiationException
實(shí)例化出錯(cuò),可能是?Class.newInstance()?或者?Constructor.newInstance()?出現(xiàn)了異常。
7簡(jiǎn)單使用反射
我們了解的反射的基本情況,能夠獲取 Class 對(duì)象,又能從 Class 中獲取相應(yīng)的信息,記下來記錄如何使用反射。
1. 創(chuàng)建類對(duì)象
通過反射創(chuàng)建類對(duì)象主要有兩種方式:通過 Class 對(duì)象的?newInstance()?方法,或通過 Constructor 對(duì)象的?newInstance()?方法。前面提到過,?newInstanse()?方法就是調(diào)用無參數(shù)的構(gòu)造方法。
第一種:Class 對(duì)象的?newInstance()?方法。
Class clz = Apple.class;Apple apple = (Apple)clz.newInstance();
第二種:Constructor 對(duì)象的?newInstance()?方法。
Class clz = Apple.class;Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
通過 Constructor 對(duì)象創(chuàng)建類對(duì)象可以選擇特定構(gòu)造方法,而通過 Class 對(duì)象則只能使用默認(rèn)的無參數(shù)構(gòu)造方法。下面的代碼就調(diào)用了一個(gè)有參數(shù)的構(gòu)造方法進(jìn)行了類對(duì)象的初始化。
Class clz = Apple.class;Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("紅富士", 15);
2. 獲取屬性
下面例子展示了使用?getDeclaredFields()?獲取全部屬性:
Class clz = Apple.class;Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
3. 調(diào)用方法
package org.su18;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Class> testClass = TestReflection.class;
Class> testClass = Class.forName("org.su18.TestReflection");
Method funTwoReflect = testClass.getDeclaredMethod("funTwo", String.class, String.class, String.class);
funTwoReflect.setAccessible(true);
funTwoReflect.invoke(null, "Kiki", "Pants", "Dance");
System.out.println("\n");
Method funOneReflect = testClass.getDeclaredMethod("funOne");
funOneReflect.invoke(null);
System.out.println("\n");
Method funThreeReflect = testClass.getDeclaredMethod("funThree", Integer[].class);
funThreeReflect.setAccessible(true);
Integer[] nums = {1,2,3,4,5,6,123,141};
int result = (int) funThreeReflect.invoke(new TestReflection(), (Object) nums);
System.out.println(result);
}
}
class TestReflection {
static void funOne() {
System.out.println("Let's dance");
}
private static void funTwo(String name, String clothes, String action) {
System.out.printf("The First Man Name Is %s .\n", name);
System.out.printf("He Wears Such Little %s .\n", clothes);
System.out.print("His Brother Was A Champion .\n");
System.out.printf("But %s Loves To %s .\n", name, action);
}
private int funThree(Integer... numbers) {
int sum = 0;
for (Integer number : numbers) {
sum += number;
}
return sum;
}
}
上例演示了在各種不同情況下調(diào)用的方式的不同結(jié)果如下:
4. 調(diào)用內(nèi)部類
假設(shè)com.reflect.Outer類,有一個(gè)內(nèi)部類 inner 和靜態(tài)內(nèi)部類 StaticInner 。那么靜態(tài)內(nèi)部類的構(gòu)造函數(shù)為Outer$StaticInner();?而普通內(nèi)部類的構(gòu)造函數(shù)為Outer$Inner(Outer outer),多了一個(gè) final 的 Outer 類型屬性,即Outer$Inner.this$0,用于存儲(chǔ)外部類的屬性值,也就是說非static內(nèi)部類保持了外部類的引用。
直接實(shí)例化內(nèi)部類方法如下:
// 靜態(tài)內(nèi)部類Outer.StaticInner sInner = new Outer.StaticInner();
// 非靜態(tài)內(nèi)部類
Outer.Inner inner = new Outer().new Inner();
內(nèi)部類的類名使用采用 $ 符號(hào),來連接外部類與內(nèi)部類,格式為outer$Inner。
String className = "com.reflect.Outer$Inner";Class.forName(className);
除了格式了差異,關(guān)于內(nèi)部類的屬性和方法操作基本相似,下面以調(diào)用該靜態(tài)類的靜態(tài)方法為例:
public static Object invokeMethod(String methodName, Class[] argsType, Object... args) {Class clazz = Class.forName("com.reflect.Outer$StaticInner");
Method method = clazz.getDeclaredMethod(methodName, argsType);
method.setAccessible(true);
return method.invoke(null, args);
8
反射的作用
有了如此強(qiáng)大的反射特性,我們可以進(jìn)行:
為所欲為:理論上可以訪問任意類任意方法任意成員變量,這一步能做的就很多,直接調(diào)用某些類的方法、在運(yùn)行中獲取某些類的某些成員變量、通過獲取注解對(duì)web框架獲取到類和地址的映射關(guān)系等等。
框架實(shí)現(xiàn):大部分的框架基本都是基于反射實(shí)現(xiàn)的。
防護(hù)繞過:針對(duì) RASP 技術(shù)防護(hù)的站,可以反射調(diào)用底層方法進(jìn)行封裝,來繞過一些攔截位置。
還有諸多作用等著大家發(fā)掘吧~
9反射的缺點(diǎn)
性能開銷
反射涉及類型動(dòng)態(tài)解析,所以 JVM 無法對(duì)這些代碼進(jìn)行優(yōu)化。因此,反射操作的效率要比那些非反射操作低得多。我們應(yīng)該避免在經(jīng)常被執(zhí)行的代碼或?qū)π阅芤蠛芨叩某绦蛑惺褂梅瓷洹?/p>安全限制
使用反射技術(shù)要求程序必須在一個(gè)沒有安全限制的環(huán)境中運(yùn)行。如果一個(gè)程序必須在有安全限制的環(huán)境中運(yùn)行,如 Applet,那么這就是個(gè)問題了。內(nèi)部曝光
由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪問私有的屬性和方法),所以使用反射可能會(huì)導(dǎo)致意料之外的副作用--代碼有功能上的錯(cuò)誤,降低可移植性。反射代碼破壞了抽象性,因此當(dāng)平臺(tái)發(fā)生改變的時(shí)候,代碼的行為就有可能也隨著變化。
雖然大家都在說反射性能低,是因?yàn)闊o法通過 JIT 進(jìn)行優(yōu)化,但現(xiàn)在的 JDK 已經(jīng)非常強(qiáng)悍了,并不至于因?yàn)樾阅荛_銷大而無法使用強(qiáng)大的反射功能。
凌天實(shí)驗(yàn)室凌天實(shí)驗(yàn)室,是安百科技旗下針對(duì)應(yīng)用安全領(lǐng)域進(jìn)行攻防研究的專業(yè)技術(shù)團(tuán)隊(duì),其核心成員來自原烏云創(chuàng)始團(tuán)隊(duì)及社區(qū)知名白帽子,團(tuán)隊(duì)專業(yè)性強(qiáng)、技術(shù)層次高且富有實(shí)戰(zhàn)經(jīng)驗(yàn)。實(shí)驗(yàn)室成立于2016年,發(fā)展至今團(tuán)隊(duì)成員已達(dá)35人,在應(yīng)用安全領(lǐng)域深耕不輟,向網(wǎng)絡(luò)安全行業(yè)頂尖水平攻防技術(shù)團(tuán)隊(duì)的方向夯實(shí)邁進(jìn)。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的java swing jbutton_Java 反射的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中l什么是ock接口
- 下一篇: java美元兑换,(Java实现) 美元