OkReflect:Kotlin 反射框架

目錄

  1. 什麼是反射?
  2. 什麼是 jOOR?
  3. 什麼是 OkReflect?
  4. OkReflect 是怎麼來的?
  5. 怎麼用 OkReflect 進行反射?
  6. OkReflect 與 jOOR 還有什麼區別?
  7. OkReflect 內部是怎麼實現的?
  8. 怎麼配置 OkReflect?
  9. 結語

在講 OkReflect 以前,咱們先來說講反射。java

1. 什麼是反射?

Java 反射機制能讓程序在運行時,對於任意一個類,都可以知道這個類的全部屬性和方法。git

對於任意一個對象,都可以調用它的任意一個方法和屬性。github

這種動態獲取的信息以及動態調用對象的方法的功能就叫作 Java 反射機制。安全

說白了, 就像是你去一家沙縣,你不吃辣,結果吧老闆作的炒粉總是放辣椒,經過反射,你就能夠吃到一份不辣的炒粉。網絡

1.1 正常建立字符串

String str = "666";
複製代碼

1.2 使用反射建立字符串

try {
    Class clazz = String.class;
    Constructor constructor = clazz.getConstructor(String.class);
    String instance = (String) constructor.newInstance("666");
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
複製代碼

這麼一看,反射也太麻煩了,有啥用?但若是你想建立的實例的構造函數是私有的,你調不到,這時候反射就派上用場了。架構

1.3 使用了反射的第三方庫

若是你是 Android 開發者,那相信你確定聽過或用過 Retrofit 和插件化技術,這兩個技術都用到了反射。框架

Retrofit 經過動態代理建立 Http 請求,以幫助咱們實現優雅地進行網絡請求。異步

而插件化技術則是對 Android 源碼進行了反射,修改一些關鍵字段和調用一些關鍵方法,讓 Android 系統改變原始的行爲,好比加載未在清單文件中聲明的 Activity。async

1.4 有效使用反射

開發者有三個層次,用輪子、懂輪子和造輪子。maven

用輪子也就是咱們可以使用 Java 、 Android 的 SDK 或其餘第三方庫進行開發,懂輪子則是對這些第三方庫的源碼有所瞭解,造輪子也就是咱們不只理解這些第三方庫,還能找到它的不足,對其進行從新開發、再次封裝或修改。

再次封裝能夠經過繼承和像 Retrofit 同樣經過動態代理實現,而修改只能經過反射來進行,由於第三方庫是不存在於咱們源碼中的。

造輪子要求你改變看待源碼的角度,看源碼的時候要思考,這種實現存在什麼問題?還有更好的實現嗎?只有這樣,你才能作到不只提升本身的效率,甚至還能提升整個技術社區的開發效率。

1.5 使用反射調用私有成員

假設咱們如今有一個 Client 類,它的構造函數和方法都是私有的,這時候咱們經過反射就能建立這個實例,而且調用它的私有方法。

public class Client {

    private String name;

    private Client(String name) {
        this.name = name;
    }
  
    private void setName(String name) {
        this.name = name;
    }

}
複製代碼
try {
    Class clazz = Class.forName("Client");
    Constructor constructor = clazz.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    Client client = (Client) constructor.newInstance("小張");
    Method method = clazz.getDeclaredMethod("setName", String.class);
    method.setAccessible(true);
    method.invoke(client, "老王");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}
複製代碼

建立類和調用方法的效果是達到了,可是這代碼看上去也太不美觀了,別怕,有 jOOR。

2. 什麼是 jOOR?

2.1 jOOR 介紹

jOOR 是一個老牌的反射框架,它和 OkReflect 同樣對 Java 反射的操做進行了封裝。

2.2 jOOR 用法

String world = on("java.lang.String")  // 至關於 Class.forName()
                .create("Hello World") // 調用構造函數建立實例
                .call("substring", 6)  // 調用 substring 方法
                .get();                // 獲取方法返回結果
複製代碼

挺好,異常都不見了,但你還真不能直接這樣用,出問題了仍是會報異常。

jOOR 自定義了 ReflectException,反射中發生的異常會被封裝成 ReflectException,因此仍是要包一層 try…catch。

2.3 處理異常

try {
    String world = on("java.lang.String")
            .create("Hello World")
            .call("substring", 6)
            .get();
} catch (ReflectException e) {
    // 處理異常
}
複製代碼

雖然仍是要包一層 try…catch,可是比最原始的反射操做是要好多了,但還有沒有更好的辦法呢?下面看 OkReflect 的處理。

3. 什麼是 OkReflect?

3.1 OkReflect 介紹

OkReflect 一個對 Java 反射操做進行了封裝的工具,並且是用 Kotlin 實現的。

3.2 OkReflect 是怎麼來的?

在《Android 組件化架構》和《Android 插件化開發指南》這兩本書中,兩位做者都提到了 jOOR 反射框架。

剛開始看到 jOOR 的時候,以爲使用 jOOR 和常規的反射操做比起來優雅不少,可是當我在不一樣的使用場景下對它進行進行一些測試時,卻發現了一些問題,OkReflect 就是在我解決這些問題的過程當中誕生的。

4. 怎麼用 OkReflect 進行反射?

4.1 使用 OkReflect 進行反射

try {
    String world = OkReflect.on("java.lang.String")
            .create("Hello World")
            .call("substring", 6)
            .get();
} catch (Exception e) {
    // 處理異常
}
複製代碼

這好像跟 jOOR 沒啥不一樣啊,用來幹嗎?別急,OkReflect 還容許你在回調中處理異常。

4.2 在回調中處理異常

String world = OkReflect.on("java.lang.String")
                .create("Hello World")
                .call("substring", 6)
                .error(new OkReflect.OkReflectErrorCallback() {
                    @Override
                    public void onError(@NotNull Exception e) {
                        // 處理異常
                    }
                })
                .get();
複製代碼

若是你用的是 Kotlin,那你還能夠這樣寫。

4.3 在 Kotlin 中使用 OkReflect

val world: String? = OkReflect.on("java.lang.String")
        .create("Hello World")
        .call("substring", 6)
        .error{
            // 處理異常
        }
        .get()
複製代碼

5. OkReflect 與 jOOR 還有什麼區別?

5.1 連續調用方法

在 jOOR 中,若是你調用的方法是有返回值的,那你下一個調用的方法就會用上一個方法返回的值來調用,當咱們並不關心返回值的時候,這種默認操做會帶來一些不便之處。假設咱們如今有一個 Manager 類。

public class Manager {

    public List<String> items = new ArrayList<>();
    public int count;

    public int addData(String data) {
        this.items.add(data);
        count ++;
        return count;
    }

    public String getData(int i) {
        return items.get(i);
    }

}

複製代碼
5.1.1 用 jOOR 連續調用方法

假如咱們用 jOOR 在添加數據後獲取數據,在調用方法後 jOOR 會拋出 NoSuchMethodException 異常,這是由於 jOOR 會使用 addData 返回的 count 來調用 getData 方法,而 count 是 Integer,Integer 中的確沒有 getData 方法。

// jOOR
String data = on("Manager")
                .create()
                .call("addData", "data")
                .call("getData", 0)
                .get();
複製代碼
5.1.2 用 OkReflect 連續調用方法

若是使用 OkReflect 進行這個操做,則不會出現這個問題,由於 OkReflect 默認是使用實例來調用方法的。

// OkReflect
String data = OkReflect.on(Manager.class)
                .create()
                .call("addData", "data")
                .call("getData", 0)
                .get();
複製代碼
5.1.3 在 OkRefelct 中使用返回值調用下一個方法

經過 callWithResult() 方法,在 OkReflect 中也能夠用上一個方法的返回值來調用下一個方法。

String str = OkReflect
                .on(String.class)
                .create("Hello world")
                .call("substring", 6)
                .callWithResult("substring", 4)
                .get();
複製代碼

5.2 獲取實例

若是你想在添加數據後忽略返回值,而是要拿到該實例時要怎麼作?在 jOOR 中你只能獲取返回值的結果,可是在 OkRefelct 中,你可使用 getInstance() 方法來獲取實例而不是返回結果,好比下面這這樣的

// OkReflect
Manager manager = OkReflect.on(Manager.class)
                    .create()
                    .call("addData", "data")
                    .getInstance();
複製代碼

5.3 類型安全

在 jOOR 中,獲取的返回值的類型是沒有保證的,也就是在 try…catch 中使用 ReflectException 的話包含的範圍還過小,要使用 Exception 才能捕獲到類型轉換異常。

可是在 OkReflect 中,在返回時對類型轉換異常進行了捕獲,若是類型不對,則會返回空,你能夠用空值來判斷是否轉換成功,也能夠在 ErrorCallback 中處理該異常,好比下面這樣的。

String manager = OkReflect.on(Manager.class)
                .create()
                .call("addData", "data")
                .error(new OkReflect.OkReflectErrorCallback() {
                    @Override
                    public void onError(@NotNull Exception e) {
                        // 處理異常
                    }
                })
                .getInstance();
複製代碼

5.4 Android final 字段

在 Android 中的 Field 類是沒有 modifiers 字段的,這致使常規的修改 final 字段的方法在 Android 中不可行。

jOOR 和 OkReflect 都經過反射修改了 Field 中的 modifiers 字段,可是 OkReflect 對系統進行了判斷,所以 OkReflect 在 Android 中也能夠修改 final 字段。

5.5 外部實例

5.5.1 調用外部實例的成員

jOOR 的 on 方法中只能傳入 class 信息,而 OkReflect 中能夠傳入實例。這有什麼用呢?好比 Android 中的 Instrumentation,本身建立一個實例進行 Hook 是很是麻煩的,因此一般都是用 Activity 中已有的 Instrumentation 進行 Hook。下面是使用實例來調用方法的例子。

Client client = new Client();
OkReflect.onInstance(client)
          .call("setName", "Alex")
          .get("name");
複製代碼

5.5.2 修改父類的私有字段

上面已經說了在 jOOR 中沒法傳入外部實例,更不用說經過外部實例修改父類的私有字段了。

而使用 OkReflect 的 with 方法就能夠實現經過外部實例修改父類的私有字段,好比想經過 Activity 實例 Hook Activity 中的 mInstrumentation 字段。

// 修改並獲取父類的私有字段
Client client = new Client();
String superName = OkReflect.on(SuperClient.class)
                .with(client)
                .set("superName", "Tom")
                .get("superName");
複製代碼

5.6 參數的類型信息

當你調用的方法實參中有空值時,不管是 jOOR 仍是 OkReflect 對沒法對參數的類型進行判斷,所以也就沒法找到對應的方法並調用。

而在 OkReflect 中,你能夠在 callWithClass() 方法中傳入對應的 class 對象,這樣 OkReflect 就能使用這些類信息找到對應的方法並進行調用。

Class classes[] = {String.class, Byte.class};
Object args[] = {"Tom", null};
String name = OkReflect.on(TestClass.class)
                .create()
                .callWithClass("setData2", classes, args)
                .get("name");
複製代碼

5.7 異步執行

若是你調用的函數處理運行時間較長,你能夠選擇用本身的異步框架執行反射,也能夠用 OkReflect 的 async 方法實現異步執行反射。

// Java
OkReflect.on(Client.class)
        .create()
        .call("setName", "Tom")
        .field("name")
        .async(result -> {
            // 處理結果
        });
複製代碼
OkReflect.on(Client::class.java)
    .create()
    .call("setName", "Tom")
    .field("name")
    .callback<String> {
        // 處理結果
    }
複製代碼

5.8 將字符串編譯成 Java 類文件

jOOR 支持經過字符串的形式生成類,而 OkReflect 沒有實現這個功能。

6. OkReflect 的內部是怎麼實現的?

6.1 單實例

每次調用 jOOR 的 call 方法時,jOOR 都會建立一個 Reflect 實例,當你須要頻繁中進行反射的時候,頻繁建立實例會形成內存抖動,嚴重時甚至會致使 OOM。

而 OkReflect 的內部從你第一次進行反射開始到後續的全部反射操做,都是在同一個 OkReflect 實例中進行,每次進行一次反射操做 OkReflect 都會清理上一次反射時加載的資源。

6.2 僞構建者模式

OkReflect 採用的是僞構建者模式,get() 方法至關於 build() 方法,若是你想要忽略 get() 方法進行反射的一些操做,你可使用 simpleSet() 和 simpleCall()。

String nameFromMethod = OkReflect.on(Client.class)
                          .create()
                          .simpleCall("getName");

String name = OkReflect.on(Clent.class)
                          .create()
                          .simpleSet("name", "Tom");
複製代碼

7. 怎麼配置 OkReflect?

7.1. Gradle 依賴

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
複製代碼
dependencies {
    implementation 'com.github.zeshaoaaa:OkReflect:master-SNAPSHOT'
}
複製代碼

7.2 Maven 依賴

<repositories>
		<repository>
		    <id>jitpack.io</id>
		    <url>https://jitpack.io</url>
		</repository>
	</repositories>
複製代碼
<dependency>
	    <groupId>com.github.zeshaoaaa</groupId>
	    <artifactId>OkReflect</artifactId>
	    <version>master-SNAPSHOT</version>
	</dependency>
複製代碼

結語

若是你須要經過 compile 將字符串轉換成 Java 類文件,你能夠選擇 jOOR,若是你須要的是其餘的反射功能,而且使用 Kotlin 進行開發,那推薦你使用 OkReflect

參考文獻

《Android 插件化開發指南》

《Android 組件化架構》

相關文章
相關標籤/搜索