反射相關知識及jOOR反射庫介紹

反射是什么

反射(Reflection)是Java程序開發語言的特征之一,它允許運行中的Java程序獲取自身的信息,并且可以操作類或對象的內部屬性。主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力,并能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。

Oracle官方對反射的解釋:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。

使用反射有如下優缺點

優點:

  1. 能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性
  2. android的開發中,反射使得在版本和機型的適配兼容性上提供了大的方便

缺點:

  1. 使用反射的性能較低
  2. 使用反射相對來說不安全(另外,對于混淆類的情況很可能對應不上)
  3. 破壞了類的封裝性(可以通過反射獲取這個類的私有方法和屬性 )

因此,反射有利有弊,需要分具體場景來擇取是否真的需要反射!!!

java虛擬機實例化一個對象的流程描述:

假如你寫了一段代碼:Object o = new Object(); 運行了起來!
其內部運行流程為如下描述:
首先JVM會啟動,你的代碼會編譯成一個.class文件,然后被類加載器加載進jvm的內存中,你的類Object加載到方法區中,創建了Object類的class對象到堆中,注意這個不是new出來的對象,而是類的類型對象,每個類只有一個class對象,作為方法區類的數據結構的接口。jvm創建對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則為你的對象分配內存,初始化也就是代碼:new Object()。

以上流程來自于這里的描述: https://www.zhihu.com/question/24304289

JDK反射介紹

在這里先看一下sun為我們提供了哪些操作反射的類:

java.lang.Class; //類對象
java.lang.reflect.Constructor; //構造器對象
java.lang.reflect.Field; //屬性對象
java.lang.reflect.Method; //方法對象
java.lang.reflect.Modifier; //修飾符對象

注:Modifier可用來描述abstractfinalinterfacenativeprivateprotectedpublicstaticstrictfpsynchronizedtransientvolatile等。

JDK反射具體有如下用法:

注意區分帶不帶Declared字樣的方法名區別:

  • 帶有Declared時表示獲取與publicprivateprotect修飾無關的所有對象,但不能是繼承來的
  • 不帶Declared時,表示只獲取public標識符修飾的對象,且還包括從超類繼承來的public對象
  1. 獲取對象構造器
//獲得指定參數類型params的public構造函數
Constructor getConstructor(Class[] params);

//獲得類的所有public構造函數
Constructor[] getConstructors(); 

//獲得使用指定參數類型params的構造函數
Constructor getDeclaredConstructor(Class[] params);

//獲得類的所有的構造函數
Constructor[] getDeclaredConstructors();
  1. 獲取類屬性字段
//獲得類中命名為name的public字段
Field getField(String name);

//獲得類的所有public字段
Field[] getFields();

//獲得類中命名為name的字段
Field getDeclaredField(String name);

//獲得類中的所有的字段
Field[] getDeclaredFields();
  1. 獲取類方法
//使用指定參數類型params,獲得名稱為name的public方法
Method getMethod(String name, Class[] params);

//獲得類中所有的public方法
Method[] getMethods();

//使用指定參數類型params,獲得命名為name的方法
Method getDeclaredMethod(String name, Class[] params);

//獲取類中的所有方法
Method[] getDeclaredMethods();

android開發中常見用法如下(其他用法不再進行測試描述,具體可以參見GH-Demo):

public static int getStatusBarHeight(Context context) {
    try {
         //構造dimen類對象
        Class<?> c = Class.forName("com.android.internal.R$dimen");
        Object obj = c.newInstance(); //使用無參構造函數實例化dimen對象
        Field field = c.getField("status_bar_height"); //獲取obj對象中名稱為status_bar_height的Field字段
        field.setAccessible(true); //修改訪問修飾屬性(假設為private修飾)
        int height = Integer.parseInt(field.get(obj).toString()); //從obj對象中讀取field字段對應的value
        return context.getResources().getDimensionPixelSize(height);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return 0;
}

下面介紹一下網上比較熱門且自認為用法比較靈活的java反射封裝類庫:jOOR

jOOR官方介紹

jOOR - Fluent Reflection in Java jOOR is a very simple fluent API that gives access to your Java Class structures in a more intuitive way. The JDK’s reflection APIs are hard and verbose to use. Other languages have much simpler constructs to access type meta information at runtime. Let us make Java reflection better. http://www.jooq.org/products

大意為:jOOR提供了一種更為直觀的方式來構建JDK原生的反射調用,因為JDK提供的反射API使用起來較冗長(它對包java.lang.reflect進行了簡單封裝,使得反射更加方便)。

gradle配置為:compile'org.jooq:joor:0.9.6'

先看一個簡單例子(同上述方法getStatusBarHeight()),對比JDK的反射API和jOOR的方法調用。

public static int getStatusBarHeight(Context context) {
    final int statusHeightId = Reflect.on("com.android.internal.R$dimen")
            .create().field("status_bar_height").get();
    return context.getResources().getDimensionPixelSize(statusHeightId);
}

從上面的例子就可以看到jOOR明顯的優勢:

  1. 簡化了反射冗長的異常處理。
  2. 簡化了對ClassMethod等反射類的實例化,改為統一Reflect替換。
  3. 支持private方法的調用,內部動態區分是否需要accessible()
  4. 將反射調用封裝為更流行的鏈式調用方式,代碼更容易理解(類似Rxjava的封裝方式)。

jOOR功能介紹

  1. 提供on()操作符對Class、對象(還包括方法、構造函數)進行統一實例化為Reflect對象,后續所有的反射操作基于該Reflect對象進行。
  2. 調用方式均被封裝成返回Reflect對象的鏈式結構,在使用上使得代碼更加簡潔。
  3. 對方法的簽名匹配封裝了更完善的匹配規則,包括精確匹配exactMethod()、近似匹配similarMethod()【對函數參數的近似匹配(int -> Integer)】和基類搜索等。
  4. 調用私有方法的不需要顯示調用setAccessible(),內部動態讀取修飾標記自動適配。
  5. 更加簡潔的實現了對象構造函數的反射調用create()方法。
  6. 函數的調用call()方法組合成了可以拼接在Reflect的對象后面的鏈式方法。
  7. 額外增加了高級操作符as(),它實現了類的代理訪問以及POJO對象get/set/is方法實現。

jOOR API介紹

  1. 通過類名轉換成一個Reflect對象
public static Reflect on(String name);
public static Reflect on(String name, ClassLoader classLoader);
public static Reflect on(Class<?> clazz);
  1. 將一個對象轉換成一個Reflect對象
public static Reflect on(Object object);
  1. 修改一個AccessibleObject類型的對象的訪問權限
public static <T extends AccessibleObject> T accessible(T accessible);
  1. 返回Reflect對象具體包裝的類型,類型為Class或者對象,具體由操作符on()的重載參數決定
public <T> T get();
  1. name指定的field轉換成一個Reflect對象,后續對field的操作變為對Reflect對象的操作
public Reflect field(String name);
  1. 返回當前Reflect對象的所有field屬性,并轉換成Reflect對象的map
public Map<String, Reflect> fields();
  1. 修改(獲取)field屬性值
public Reflect set(String name, Object value);
public <T> T get(String name);
  1. 反射調用name指定的函數,此函數封裝了對函數的簽名的精準匹配和近似匹配
public Reflect call(String name);
public Reflect call(String name, Object... args);
  1. 反射調用指定類的構造函數,封裝了精準匹配和近似匹配
public Reflect create();
public Reflect create(Object... args);
  1. 返回當前Reflect對象封裝的對象類型
public Class<?> type();
  1. 給封裝對象創建一個代理訪問,還實現了對POJO對象setXX/getXX/isxx功能(此為Reflect對象的高級功能)
public <P> P as(Class<P> proxyType);

jOOR 典型用法

先列出測試舉例TestField類定義以便有個大概的概念:
聲明實例:TestField testField = new TestField();

public static class TestField {
    /**
     * private
     */
    private int INT1 = new Integer(0);
    private Integer INT2 = new Integer(2);


    /**
     * private & static
     */
    private static int S_INT1 = new Integer(3);
    private static Integer S_INT2 = new Integer(0);

    /**
     * private & final
     */

    private final int F_INT3 = new Integer(4);

    /**
     * object
     */
    private TestField I_DATA;


    public void tetProxy(String message) {
        Log.d("TestJOOR", "tetProxy: " + message);
    }
}

代理接口POJOInterface的定義:

public interface POJOInterface {

    String substring(int beginIndex, int endIndex);

    void tetProxy(String message);

    void setFoo(String foo);

    String getFoo();

    void setBaz(String baz);

    String getBaz();
}

POJO對象的Map實現:
構造實例 Map<String, Object> map = new TestBean();

private class TestBean extends HashMap<String, Object> {

    private String baz;

    public void setBaz(String baz) {
        this.baz = "POJO-MyMap: " + baz;
    }

    public String getBaz() {
        return baz;
    }
}
  1. 調用String的非靜態方法(通過實例對象構造Reflect
String value = "1234";
String subString  = Reflect.on((Object) value).call("substring", 0, 2).get();
//結果為:subString = "123";
  1. 調用String的靜態方法(通過String.class類名構造Reflect
String valueOf = Reflect.on(String.class).call("valueOf", true).get();
//結果為:valueOf = "true";
  1. 修改private屬性
int init_int = Reflect.on(testField).get("INT2"); //init_int=2;
Reflect setReflect = Reflect.on(testField).set("INT2", 300);
int result = setReflect.field("INT2").get(); //result = 300;
  1. 修改static屬性
int sInit_int = Reflect.on(TestField.class).get("S_INT1"); //sInit_int = 3;
Reflect obj = Reflect.on("com.android.test.joor.TestJOOR$TestField").set("S_INT2", 500);
int sInt2 = obj.field("S_INT2").get(); //sInt2 = 500;
  1. 復雜鏈式修改多個屬性值
Reflect.on(testField).set("I_DATA", Reflect.on(TestField.class).create())
            .field("I_DATA").set("INT1", 700).set("INT2", 800);
  1. interface實現類對象的代理
String asResult = Reflect.on((Object) "abc").as(POJOInterface.class).substring(1, 2);
//測試結果: asResult = "bc";

Reflect.on(testField).as(POJOInterface.class).tetProxy("this is proxy test!!");
//測試結果:調用TestField類中的tetProxy()函數。
  1. 對于沒有實現set/get方法的Bean對象,對應的數據有以key-valuekeysetXx()xx后綴)的形式存放在HashMap
Reflect.on(map).as(POJOInterface.class).setFoo("abc");
int size = map.size(); //size = 1;
String value1 = (String) map.get("foo"); //value1 = "abc";
String value2 = Reflect.on(map).as(POJOInterface.class).getFoo(); //value2 = "abc";
  1. 已經實現了set/get方法的Bean對象,對應數據存放在Bean對象的字段中
Reflect.on(map).as(POJOInterface.class).setBaz("baz");
int size = map.size(); //size = 0; 沒有存放在hasMap中
String value4 = (String) map.get("baz"); //value4 = null;
String value5 = Reflect.on(map).as(POJOInterface.class).getBaz(); //value5 = "MyMap: baz-test";

備注:
jOOR對反射的原始異常進行了轉義---ReflectException,其直接基類為RuntimeException,這里意味著通過jOOR庫的api開發時,不會強制開發人員用try...catch括起來,但一旦發生異常,程序就會中斷運行

反射調用效率優化

從應用層面看,想對反射的執行效率做提升優化,只能在項目中明令禁止使用反射了,如果不可規避(一般避免不了),只能最大限度的降低反射的調用次數了。一般的做法是將第一次獲取的ClassMethodField對象進行緩存起來,下次調用同樣的反射對象時直接取已緩存對象進行相應調用。這里引用另一個反射調用的封裝庫 reflect,其內部就是將ClassMethodField等對象進行緩存以備下次調用,但個人認為其用法不如jOOR靈活,但優化思路值得參考。
本人參考reflect庫的優化思路對jOOR內部進行修改,在不破壞外部調用接口的前提下對內部的ClassConstructorMethodField進行緩存。地址為GH-Demo-Reflect.java

參考文檔

https://www.zhihu.com/question/24304289
https://github.com/masonTool/reflect

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374