反射
定义
Java 的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
用途
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的或者只对系统应用开放,这时候就可以利用 Java 的反射机制,通过反射来获取所需的私有成员或方法。
反射机制的相关类
类名 | 用途 |
---|---|
Class 类 | 代表类的实体,在运行的 Java 应用程序中表示类和接口 |
Field 类 | 代表类的成员变量(成员变量也称为类的属性) |
Method 类 | 代表类的方法 |
Constructor 类 | 代表类的构造方法 |
Class 类
获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field 类
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method 类
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
Constructor 类
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
带有 Declared 修饰的方法可以反射到私有的方法,没有 Declared 修饰的只能用来反射公有的方法。
示例
-
简单示例
首先创建一个 Npc 类(被反射的类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
package dev.svip.reflect; public class Npc { //静态field public static int NPC_TYPE_1 = 1; //私有成员变量 private int npcType; //公有成员变量 public String name; //无参构造函数 public Npc(){ } //有参构造函数 public Npc(int npcType, String name){ this.npcType = npcType; this.name = name; } public int getNpcType(){ return npcType; } public void setNpcType(int npcType){ this.npcType = npcType; } public String getName(){ return name; } public void setName(String name){ this.name = name; } //静态方法 public static void sayHello(String word){ System.out.println("hello " + word); } }
反射代码封装在 ClazzTest.java 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
package dev.svip.reflect; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ClazzTest { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, InstantiationException { /* 获取Class对象 */ //第一种方式获取Class对象 Npc npc1 = new Npc(); Class<? extends Npc> npcClazz1 = npc1.getClass(); System.out.println(npcClazz1.getName()); //第二种方式获取Class对象 Class<Npc> npcClazz2 = Npc.class; //判断第一种和第二种方式获得的Class对象是否为同一个 System.out.println(npcClazz2 == npcClazz1); //第三种方式获取Class对象 try { Class<?> npcClazz3 = Class.forName("dev.svip.reflect.Npc"); //判断第一种和第三种方式获得的Class对象是否为同一个 System.out.println(npcClazz3 == npcClazz1); } catch (ClassNotFoundException e) { e.printStackTrace(); } /* *访问方法 */ Npc npc = new Npc(1, "妲己"); Class<Npc> npcClazz = Npc.class; //第一个参数是方法名,第二个参数是函数的参数class对象,因为存在重载的可能性,用参数类型区分 Method sayHello = npcClazz.getMethod("sayHello", String.class); sayHello.invoke(npc, "world"); /* * 访问类方法 */ System.out.println(npc.getName()); Method setName = npcClazz.getMethod("setName", String.class); setName.invoke(npc, "鲁班"); System.out.println(npc.getName()); /* * 获取字段,读取字段的值 */ //获取公有成员 Field field = npcClazz.getField("name"); field.setAccessible(true); System.out.println(field.get(npc)); //获取私有成员 field = npcClazz.getDeclaredField("npcType"); field.setAccessible(true); System.out.println(field.get(npc)); /* * 获取构造函数,创建实例 */ Constructor<Npc> declaredConstructor = npcClazz.getDeclaredConstructor(int.class, String.class); npc = declaredConstructor.newInstance(1, "后羿"); System.out.println(npc.getName()); /* * 获取继承的父类 */ Class<? super Npc> superclass = npcClazz.getSuperclass(); System.out.println(superclass.getName()); /* * 获取注解 */ Annotation[] annotations = npcClazz.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation.getClass().getName()); } } }
-
成熟项目
GravityBox 项目中使用了大量的反射,可以在代码中通过搜索反射相关的方法进行查看。
Android 实战
JNI 调用 Java
在之前文章的easymd5项目中,我们通过 Java 调用 Native 层代码,现在,我们将在 Native 层通过反射调用 Java 代码。
首先在 Android Studio 中新建一个 Native C++ 项目。Java 层代码如下。
|
|
我们定义了两个 native 层方法:Refmd5 和 Refmd5Second,并通过 Java 实现了一个计算 md5 的方法 Javamd5。
Refmd5 的 C++ 实现如下
|
|
注释为等效的 Java 代码。从中我们可以看出,在 JNI 中,首先通过反射获取到类,然后获取方法,最后通过 CallStaticObjectMethod 调用方法,从而实现对应的 Java 代码的功能。
Refmd5Second 实现如下。
|
|
两个方法实现的功能都是计算字符串的 md5,区别在于:Refmd5 的实现调用了 JDK 自带的类提供的方法,而 Refmd5Second 的实现调用了我们自己定义的类方法,即 Javamd5。
Unidbg 调用 so 文件
最终生成的 apk 可点击此处下载,解压后将 lib 目录下的 so 文件复制到 unidbg 目录中即可。
此处我们使用 64 位,将 so 文件放置在 unidbg-android/src/test/resources/example_binaries/arm64-v8a/libreflectionmd5.so 中。
在 unidbg 中新建一个类来主动调用 so 文件中的方法。
|
|
可以看出,对 Refmd5 的调用运行成功,而对 Refmd5Second 的调用失败,提示不支持的操作,抛出的异常中正是我们自己定义的 Javamd5 方法。同样都是通过反射调用 Java 方法,为什么第一个能够成功呢?这是因为 Unidbg 帮我们实现了 Java 官方库中的方法,我们可以在 AbstractJni.java 中找到相关的实现。
所以要解决上述报错,我们只需要将缺失的 Javamd5 补全,然后重写 callObjectMethodV 即可。
|
|
onCreate Native 化
同理,我们也可以将 onCreate 进行 Native 化,jni 代码如下。
|
|
其中,super.onCreate(savedInstanceState);
还有以下两种实现方式,主要区别在于对父类的寻找上。
|
|
|
|
然后将 MainActivity 中的 onCreate 方法修改为以下即可
|
|
此时我们已无法通过 jadx 看到 onCreate 的实现。
另外,我们还可以将方法都进行动态注册,从而增大分析难度,具体可参考动态注册部分
本文中 Android 项目代码请访问:https://github.com/svipblog/ReflectionMd5
参考资料
1、https://www.jianshu.com/p/9be58ee20dee