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 组件化架构》

相关文章
相关标签/搜索