目录
- 什么是反射?
- 什么是 jOOR?
- 什么是 OkReflect?
- OkReflect 是怎么来的?
- 怎么用 OkReflect 进行反射?
- OkReflect 与 jOOR 还有什么区别?
- OkReflect 内部是怎么实现的?
- 怎么配置 OkReflect?
- 结语
在讲 OkReflect 之前,我们先来讲讲反射。
1. 什么是反射?
Java 反射机制能让程序在运行时,对于任意一个类,都能够知道这个类的所有属性和方法。
对于任意一个对象,都能够调用它的任意一个方法和属性。
这种动态获取的信息以及动态调用对象的方法的功能就叫做 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。
1.4 有效使用反射
开发者有三个层次,用轮子、懂轮子和造轮子。
用轮子也就是我们能够使用 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 组件化架构》