1. 程式人生 > >java梳理-反射

java梳理-反射

cell 時間 array 文件 所表 todo java方法 clas java.net

本文屬於面試題梳理系列:問題:java反射類的訪問私有方法與普通方法相比,須要多處理什麽? 之前梳理類載入的時候,介紹到初始化的時機之中的一個:用java.lang.reflect包的方法對類進行反射調用的時候,假設類沒有進行過初始化。則須要先觸發其初始化。以下梳理相關知識。文件夾例如以下: 1.什麽是JAVA的反射機制
2.JAVA反射機制API及功能 獲取類的Class對象
獲取類的Fields
獲取類的Method
獲取類的Constructor
新建類的實例
Class<T>的函數newInstance
通過Constructor對象的方法newInstance
3調用類的函數
調用private函數
4設置/獲取類的屬性值
private屬性
以下段落展開詳述。
什麽是JAVA的反射機制

Java反射是Java被視為動態(或準動態)語言的一個關鍵性質。

這個機制同意程序在執行時透過Reflection APIs取得不論什麽一個已知名稱的class的內部信息,包含其modifiers(諸如public, static 等)、superclass(比如Object)、實現之interfaces(比如Cloneable)。也包含fields和methods的全部信息,並可於執行時改變fields內容或喚起methods。

Java反射機制容許程序在執行時載入、探知、使用編譯期間全然未知的classes。換言之,Java能夠載入一個執行時才得知名稱的class,獲得其完整結構。

這樣的“看透”class的能力(the ability of the program to examine itself)被稱為introspection(內省、內觀、反省)。Reflection和introspection是常被並提的兩個術語。


java反射API及功能 在JDK中,主要由下面類來實現Java反射機制,這些類(除了第一個)都位於java.lang.reflect包中

  Class類:代表一個類,位於java.lang包下。

  Field類:代表類的成員變量(成員變量也稱為類的屬性)。

  Method類:代表類的方法。

  Constructor類

:代表類的構造方法。

  Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法。

獲取類的Class對象:

要想使用反射,首先須要獲得待操作的類所相應的Class對象。

  Java中,不管生成某個類的多少個對象。這些對象都會相應於同一個Class對象。

  這個Class對象是由JVM生成的,通過它可以獲悉整個類的結構。

經常使用的獲取Class對象的3種方式:

  1).使用Class類的靜態方法。比如:  Class.forName("java.lang.String");

 	2).使用類的.class語法。如: 	String.class;
    3).使用對象的getClass()方法。如:
	String str = "aa";
	Class<?

> classType1 = str.getClass();

技術分享

獲取類的Fields

能夠通過反射機制得到某個類的某個屬性,然後改變相應於這個類的某個實例的該屬性值。JAVA 的Class<T>類提供了幾個方法獲取類的屬性。

public FieldgetField(String name)返回一個 Field 對象,它反映此 Class 對象所表示的類或接口的指定公共成員字段
public Field[] getFields()返回一個包括某些 Field 對象的數組,這些對象反映此 Class 對象所表示的類或接口的全部可訪問公共字段
public FieldgetDeclaredField(Stringname)返回一個 Field 對象,該對象反映此 Class 對象所表示的類或接口的指定已聲明字段
public Field[] getDeclaredFields()

返回 Field 對象的一個數組,這些對象反映此 Class 對象所表示的類或接口所聲明的全部字段

public static void main(String[] args) throws ClassNotFoundException { // TODO Auto-generated method stub Class<?> pc3 = Class.forName("reflect.Person"); Field[] fields=pc3.getFields(); for (Field f : fields) { System.out.println("field:"+f); } Field[] fields2= pc3.getDeclaredFields(); for (Field f : fields2) { System.out.println("d field:"+f); } }

執行結果:
field:public java.lang.String reflect.Person.name
d field:private java.lang.String reflect.Person.password
d field:public java.lang.String reflect.Person.name
 可見getFields和getDeclaredFields差別:

getFields返回的是申明為public的屬性,包含父類中定義。

getDeclaredFields返回的是指定類定義的全部定義的屬性,不包含父類的。

獲取類的Method

通過反射機制得到某個類的某個方法,然後調用相應於這個類的某個實例的該方法

Class<T>類提供了幾個方法獲取類的方法。

public MethodgetMethod(String name,Class<?>... parameterTypes)

返回一個 Method 對象,它反映此 Class 對象所表示的類或接口的指定公共成員方法

public Method[] getMethods()

返回一個包括某些 Method 對象的數組。這些對象反映此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的公共 member 方法

public MethodgetDeclaredMethod(Stringname,Class<?>... parameterTypes)

返回一個 Method 對象。該對象反映此 Class 對象所表示的類或接口的指定已聲明方法

public Method[] getDeclaredMethods()

返回 Method 對象的一個數組。這些對象反映此 Class 對象表示的類或接口聲明的全部方法,包含公共、保護、默認(包)訪問和私有方法,但不包含繼承的方法

技術分享 依據輸出。註意getMethods與getDeclaredMethods的差別。

獲取類的Constructor

通過反射機制得到某個類的構造器,然後調用該構造器創建該類的一個實例

Class<T>類提供了幾個方法獲取類的構造器。

public Constructor<T> getConstructor(Class<?

>... parameterTypes)

返回一個 Constructor 對象,它反映此 Class 對象所表示的類的指定公共構造方法

public Constructor<?>[] getConstructors()

返回一個包括某些 Constructor 對象的數組。這些對象反映此 Class 對象所表示的類的全部公共構造方法

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

返回一個 Constructor 對象。該對象反映此 Class 對象所表示的類或接口的指定構造方法

public Constructor<?>[] getDeclaredConstructors()

返回 Constructor 對象的一個數組。這些對象反映此 Class 對象表示的類聲明的全部構造方法。它們是公共、保護、默認(包)訪問和私有構造方法

Constructor<?>[] cons=pc3.getConstructors(); for (int i = 0; i < cons.length; i++) { System.out.println("cons: "+cons[i]); }

輸出: cons: public reflect.Person() cons: public reflect.Person(java.lang.String) 生成對象: 1.先獲得Class對象,然後通過該Class對象的newInstance()方法直接生成就可以: 2.先獲得Class對象。然後通過該對象獲得相應的Constructor對象。再通過該Constructor對象的newInstance()方法生成 3.帶參 技術分享 3.調用方法: 通過反射獲取類Method對象。調用Field的Invoke方法調用函數。 技術分享
通過輸出,能夠看出setName的方法能夠調用。可是私有方法調用失敗。也就是本文最開始的問題。

4設置/獲取類的屬性值

通過反射獲取類的Field對象,調用Field方法設置或獲取值


技術分享 跟上面類似。訪問私有屬性失敗。 一問題解答:訪問私有方法,須要調用Class.getDeclaredMethod(String name,Class[] parameterTypes)或者Class.getDeclaredMethods()方法。方法Class.getMethod(String name,Class[] parameterTypes)和Class.getMethods()只返回公有方法。

此外還須要設置訪問權限。

例如以下: Method method2 = pc3.getDeclaredMethod("setPri",String.class); method2.setAccessible(true); method2.invoke(pp, "test"); 為什麽呢? 二須要結合method的invoke方法源代碼去看下。

技術分享
2.1.先檢查 AccessibleObject的override屬性是否為true。
AccessibleObject是Method,Field,Constructor的父類,override屬性默覺得false,可調用setAccessible方法改變,假設設置為true,則表示能夠忽略訪問權限的限制,直接調用。
2.2.假設不是ture,則要進行訪問權限檢測。用Reflection的quickCheckMemberAccess方法先檢查是不是public的,假設不是再用Reflection.getCallerClass(1)方法獲得到調用這種方法的Class,然後做是否有權限訪問的校驗,校驗之後緩存一次。以便下次假設還是這個類來調用就不用去做校驗了。直接用上次的結果,(非常奇怪用這樣的方式緩存,由於這樣的方式假設下次換個類來調用的話,就不用會緩存了,而再驗證一遍,把這次的結果做為緩存。但上一次的緩存結果就被沖掉了。這是一個非常easy的緩沖機制,僅僅適用於一個類的反復調用)。
2.3.調用MethodAccessor的invoke方法。每一個Method對象包括一個root對象,root對象裏持有一個MethodAccessor對象。我們獲得的Method獨享相當於一個root對象的鏡像,全部這類Method共享root裏的MethodAccessor對象,(這個對象由ReflectionFactory方法生成,ReflectionFactory對象在Method類中是static final的由native方法實例化)。


深入分析MethodAccessor的創建機制   能夠看到Method.invoke()實際上並非自己實現的反射調用邏輯,而是托付給sun.reflect.MethodAccessor來處理。
每一個實際的Java方法僅僅有一個相應的Method對象作為root,這個root是不會暴露給用戶的,而是每次在通過反射獲取Method對象時新創建Method對象把root包裝起來再給用戶。

在第一次調用一個實際Java方法相應得Method對象的invoke()方法之前,實現調用邏輯的MethodAccessor對象還沒創建;等第一次調用時才新創建MethodAccessor並更新給root,然後調用MethodAccessor.invoke()真正完畢反射調用。

那麽MethodAccessor是啥呢? jdk源代碼沒有了,從openjdk看下源代碼。 技術分享
能夠看到它僅僅是一個單方法接口,其invoke()方法與Method.invoke()的相應。 再沿著上面的分析。看看MethodAccessor是怎麽創建的。代碼片段分析: 技術分享 推斷為空 要先reflectionFactory創建, 技術分享 假設noInflation的屬性為true則直接返回MethodAccessorGenerator創建的一個MethodAccessor。否則返回DelegatingMethodAccessorImpl。並將他與一個NativeMethodAccessorImpl互相引用。 實際的MethodAccessor實現有兩個版本號,一個是Java實現的,還有一個是native code實現的。Java實現的版本號在初始化時須要較多時間,但長久來說性能較好;native版本號正好相反。啟動時相對較快。但執行時間長了之後速度就比只是Java版了。這是HotSpot的優化方式帶來的性能特性,同一時候也是很多虛擬機的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯。於是執行時間長了之後反而是托管版本號的代碼更快些。

為了權衡兩個版本號的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調用時。開頭若幹次使用native版。等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類。生成當中的invoke()方法的字節碼,以後對該Java方法的反射調用就會使用Java版。

以下相應源代碼:
技術分享

每次NativeMethodAccessorImpl.invoke()方法被調用時,都會添加一個調用次數計數器,看超過閾值沒有;一旦超過,則調用MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,而且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。興許經由DelegatingMethodAccessorImpl.invoke()調用到的就是Java版的實現了。註意到關鍵的invoke0()方法是個native方法。它在HotSpot VM裏是由JVM_InvokeMethod()函數所支持的,扯個額外話:想想大多數java有關的東西,假設你願意去看源代碼分析的話。總會找到jvm這層。當然還有大神繼續研究Hotspot的裏面的C源代碼,我認為主要知識點到這一層就算是比較深入了 。

再看DelegatingMethodAccessorImpl:

技術分享

這是一個間接層,方便在native與Java版的MethodAccessor之間實現切換。

native結束,再看java版本號:MethodAccessorGenerator

它的基本工作就是在內存裏生成新的專用Java類。並將其載入。

 private static synchronized String generateName(boolean isConstructor,
                                                    boolean forSerialization)
    {
        if (isConstructor) {
            if (forSerialization) {
                int num = ++serializationConstructorSymnum;
                return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
            } else {
                int num = ++constructorSymnum;
                return "sun/reflect/GeneratedConstructorAccessor" + num;
            }
        } else {
            int num = ++methodSymnum;
            return "sun/reflect/GeneratedMethodAccessor" + num;
        }
    }

上面貼出來的方法就是產生名字的。至於大神RednaxelaFX貼出來解釋GeneratedMethodAccessor1 這段看不懂。如有大神理解,還請多多留言指正。

http://rednaxelafx.iteye.com/blog/548536

****************總結******************** JAVA反射原理分析及JAVA反射的應用待整理。
參考: http://blog.csdn.net/yongjian1092/article/details/7364451 http://www.cnblogs.com/mengdd/archive/2013/01/26/2878136.html http://www.cnblogs.com/onlywujun/p/3519037.html http://rednaxelafx.iteye.com/blog/548536

java梳理-反射