virtualapp-RefClass反射机制(转载)
文章目录
- 声明
- 使用
- 实现分析
- 结构
- 运作
- 初始化
- 反射结构定义
- 1. RefInt
- 2. RefStaticInt
- 3.RefObject
- 4. RefMethod和@MethodParams
- 5. 小结
转自:https://www.jianshu.com/p/d040c7f1a46f
声明
package mirror.android.app;public class ContextImpl {public static Class<?> TYPE = RefClass.load(ContextImpl.class, "android.app.ContextImpl");@MethodParams({Context.class})public static RefObject<String> mBasePackageName;public static RefObject<Object> mPackageInfo;public static RefObject<PackageManager> mPackageManager;public static RefMethod<Context> getReceiverRestrictedContext;
}
上述类是VirtualApp
里对ContextImpl
类的反射的定义。
public static Class<?> TYPE = RefClass.load(ContextImpl.class, "android.app.ContextImpl");
这句是实际的初始化入口。第二个参数指定反射的操作目标类为android.app.ContextImpl
。这个是框架的硬性要求。
接下来几个都是对要反射的变量。分别对应实际的ContextImpl
类内部的mBasePackageName
、mPackageInfo
、mPackageManager
、getReceiverRestrictedContext
成员和方法。
注意,这里只有声明的过程,没有赋值的过程。这个过程,便完成了传统的查找目标类内的变量域、方法要干的事情。从代码上看,相当的简洁直观。
下面这个表格,能更直观形象的表现它的优雅:
除了形式上略有差异,两个类之间的结构上是保持一一对应的!
使用
接着,查找到这些变量域和方法后,当然是要用它们来修改内容,调用方法啦,怎么用呢:
// 修改mBasePackageName内的值
ContextImpl.mBasePackageName.set(context, hostPkg);// .....// 调用getReceiverRestrictedContext方法
Context receiverContext = ContextImpl.getReceiverRestrictedContext.call(context);
用起来是不是也相当直观?一行代码,就能看出要做什么事情。比起最开始提及的那种方式,这种方式简直清晰简洁得不要不要的,一鼓作气读下来不带停顿的。这样的代码几乎没有废话,每一行都有意义,信息密度杠杠的。
到这里就讲完了声明和使用这两个步骤。确实很简单吧?接下来再来看看实现。
实现分析
结构
摆在中间的RefClass
是最核心的类。
围绕在它周边的RefBoolean
、RefConstructor
、RefDouble
、RefFloat
、RefInt
、RefLong
、RefMethod
、RefObject
、RefStaticInt
、RefStaticMethod
、RefStaticObject
则是用于声明和使用的反射结构的定义。从名字也能直观的看出该反射结构的类型信息,如构造方法、数据类型、是否静态等。
在右边角落的两个小家伙MethodParams
、MethodReflectParams
是用于定义方法参数类型的注解,方法相关的反射结构的定义会需要用到它。它们两个的差别在于,MethodParams
接受的数据类型是Class<?>
,而MethodReflectParams
接受的数据类型是字符串,对应类型的全描述符,如android.app.Context
,这个主要是服务于那些Android SDK没有暴露出来的,无法直接访问到的类。
这里有个很重要的特点,如果没有
MethodParams
或MethodReflectParams
,也就是无参时,我们定义的RefMethod
变量名一定要和原始的保持同名,参看以下代码:
public RefStaticMethod(Class<?> cls, Field field) throws NoSuchMethodException {if (field.isAnnotationPresent(MethodParams.class)) {。。。。。} else if (field.isAnnotationPresent(MethodReflectParams.class)) {。。。。。this.method.setAccessible(true);} else {for (Method method : cls.getDeclaredMethods()) { if (method.getName().equals(field.getName())) { // 同名才能匹配this.method = method;this.method.setAccessible(true);break;}}}if (this.method == null) {throw new NoSuchMethodException(field.getName());}}
因为它是同名配置的
运作
初始化
从上面的表格可以知道,RefClass
是整个声明中最外层的结构。这整个结构要能运作,也需要从这里开始,逐层向里地初始化。上文也提到了,RefClass.load(Class mappingClass, Class<?> realClass)
是初始化的入口。初始化的时机呢?我们知道,Java虚拟机在加载类的时候,会初始化静态变量,定义里的TYPE = RefClass.laod(...)
就是在这个时候执行的。也就是说,当我们需要用到它的时候,它才会被加载,通过这种方式,框架具备了按需加载的特性,没有多余的代码。
入口知道了,我们来看看RefClass.load(Class<?> mappingClass, Class<?> realClass)
内部的逻辑。
先不放源码,简单概括一下:
1.在mappingClass
内部,查找需要初始化的反射结构(如RefObject<String> mBasePackageName
)
2.实例化查到到的反射结构变量(即做了RefObject<String> mBasePackageName = new RefObject<String>(...)
)
查找,就需要限定条件范围。结合定义,可以知道,要查找的反射结构,具有以下特点:
1.静态成员
2.类型为Ref*
查找的代码如下:
public static Class load(Class mappingClass, Class<?> realClass) {// 遍历一遍内部定义的成员Field[] fields = mappingClass.getDeclaredFields();for (Field field : fields) {try {// 如果是静态类型if (Modifier.isStatic(field.getModifiers())) {// 且是反射结构, REF_TYPES的value是注册器(Constructor)对象,通过newInstance()创建对象Constructor<?> constructor = REF_TYPES.get(field.getType()); //field.getType() 返回一个Class对象if (constructor != null) {// 实例化该成员field.set(null, constructor.newInstance(realClass, field));// 调用RefStaticMethod类中的RefStaticMethod(Class<?> cls, Field field) }}} catch (Exception e) {// Ignore}}return realClass;
}
这其实就是整个RefClass.load(...)
的实现了。可以看到,实例化的过程仅仅是简单的调用构造函数实例化对象,然后用反射的方式赋值给该变量。
REF_TYPES
是一个Map
,里面注册了所有的反射结构(Ref*
)。Class对象==>注册器(Constructor)对象
源码如下:
private static HashMap<Class<?>,Constructor<?>> REF_TYPES = new HashMap<Class<?>, Constructor<?>>();
static {try {REF_TYPES.put(RefObject.class, RefObject.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefMethod.class, RefMethod.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefInt.class, RefInt.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefLong.class, RefLong.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefFloat.class, RefFloat.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefDouble.class, RefDouble.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefBoolean.class, RefBoolean.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefStaticObject.class, RefStaticObject.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefStaticInt.class, RefStaticInt.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefStaticMethod.class, RefStaticMethod.class.getConstructor(Class.class, Field.class));REF_TYPES.put(RefConstructor.class, RefConstructor.class.getConstructor(Class.class, Field.class));}catch (Exception e) {e.printStackTrace();}
}
发现没有?在RefClass.laod(...)
里,实例化的过程简单到不可思议?因为每个反射结构代表的含义都不一样,初始化时要做的操作也各有不同。与其将这些不同都防止load的函数里,还不如将对应的逻辑分解到构造函数里更合适。这样既降低了RefClass.laod(...)
实现的复杂度,保持了简洁,也将特异代码内聚到了对应的反射结构Ref*
中去。
反射结构定义
挑几个有代表性的反射结构来分析。
1. RefInt
RefInt
这种是最简单的。依旧先不放源码。先思考下,对于一个这样的放射结构,需要关心的东西有什么?
1.首先是这个反射结构映射到原始类中是哪个Field
2.紧接着就是Field
的类型是什么。
上文表格里可以看到,反射结构的名称和实际类中对应的Field
的名称的一一对应的。我们只要拿到反射结构的名称就可以了。
Field
的类型,由于RefInt
直接对应到了int
类型,所以这个是直接可知的信息。
public RefInt(Class cls, Field field) throws NoSuchFieldException {this.field = cls.getDeclaredField(field.getName());this.field.setAccessible(true);
}
源码里也是这么做的,从反射结构的Field
里,取得反射结构定义时的名字,用这个名字去真正的类里,查找到对应的Field
,并设为可访问的,然后作为反射结构的成员变量持有了。
为了方便使用,又新增了get
、set
两个方法,便于快捷的存取这个Field
内的值。如下:
ublic int get(Object object) {try {return this.field.getInt(object);} catch (Exception e) {return 0;}
}public void set(Object obj, int intValue) {try {this.field.setInt(obj, intValue);} catch (Exception e) {//Ignore}
}
就这样,RefInt
就分析完了。这个类的实现依旧保持了一贯的简洁优雅。
2. RefStaticInt
RefStaticInt
在RefInt
的基础上,加了一个限制条件:该变量是静态变量,而非类的成员变量。熟悉反射的朋友们知道,通过反射Field
是没有区分静态还是非静态的,都是调用Class.getDeclaredField(fieldName)
方法。所以这个类的构造函数跟RefInt
是一毛一样毫无差别的。
public RefStaticInt(Class<?> cls, Field field) throws NoSuchFieldException {this.field = cls.getDeclaredField(field.getName());this.field.setAccessible(true);
}
当然,熟悉反射的朋友也知道,一个Field
是否静态是能够根据Modifier.isStatic(field.getModifiers())
来判定的。这里若是为了严格要求查找到的Feild
一定是static field
的话,可以加上这个限制优化下。
静态变量和成员变量在通过反射进行数据存取则是有差异的。成员变量的Field
需要传入目标对象,而静态变量的Field
不需要,传null
即可。这个差异,对应的get
、set
方法也做了调整,不再需要传入操作对象。源码如下:
public int get() {try {return this.field.getInt(null);} catch (Exception e) {return 0;}
}public void set(int value) {try {this.field.setInt(null, value);} catch (Exception e) {//Ignore}
}
3.RefObject
RefObject<T>
跟RefInt
相比,理解起来复杂了一点:Field
的数据类型由泛型的<T>
提供。但实际上,和RefStaticInt
一样,构造函数类并没有做严格的校验,即运行时不会在构造函数检查实际的类型和泛型里的期望类型是否一致。所以,构造函数依旧没什么变化。
public RefObject(Class<?> cls, Field field) throws NoSuchFieldException {this.field = cls.getDeclaredField(field.getName());this.field.setAccessible(true);
}
实际上,要做严格检查也依旧是可以的。我猜想,我猜想作者之所以没有加严格的检查,一是为了保持实现的简单,二是这种错误,属于定义的时候的错误,即写出了bug,那么在接下来的使用中一样会报错,属于开发过程中必然会发现的bug,因此实现上做严格的校验意义不大。
泛型<T>
的作用在于数据存取的时候,做相应的类型规范和转换。源码如下:
public T get(Object object) {try {return (T) this.field.get(object);} catch (Exception e) {return null;}
}public void set(Object obj, T value) {try {this.field.set(obj, value);} catch (Exception e) {//Ignore}
}
4. RefMethod和@MethodParams
最后再分析下RefMethod
这个Method
相关的反射结构,与之类似的有RefConstructor
、RefStaticeMethod
,实现原理上也是大同小异。
和前面Field
相关的反射结构不同,Method
的反射结构确实稍微复杂了一丢丢。RefMethod
对应的是方法,对方法来说,它有方法名、返回值、参数这三个信息要关心。
前面分析可知,变量名信息是通过反射结构定义的名字来确定的,方法名也一样,通过反射结构的Field
就能获取到。
返回值呢?所有的Method.invoke(...)
都有一个返回值,和RefObject<T>
一样,类型信息通过泛型提供,在使用的时候,仅仅做了转义。
参数这个信息,则是Method.invoke(...)
调用里必不可少的参数。VirtualApp
通过给RefMethod
定义加注解创造性地解决了这个问题,即实现了声明式,也保证了实现的简单优雅。理解这段代码不难,但这个用法确实很新颖。
看下构造方法的源码:
public RefMethod(Class<?> cls, Field field) throws NoSuchMethodException {if (field.isAnnotationPresent(MethodParams.class)) {Class<?>[] types = field.getAnnotation(MethodParams.class).value();for (int i = 0; i < types.length; i++) {Class<?> clazz = types[i];if (clazz.getClassLoader() == getClass().getClassLoader()) {try {Class.forName(clazz.getName());Class<?> realClass = (Class<?>) clazz.getField("TYPE").get(null);types[i] = realClass;} catch (Throwable e) {throw new RuntimeException(e);}}}this.method = cls.getDeclaredMethod(field.getName(), types);this.method.setAccessible(true);} else if (field.isAnnotationPresent(MethodReflectParams.class)) {String[] typeNames = field.getAnnotation(MethodReflectParams.class).value();Class<?>[] types = new Class<?>[typeNames.length];for (int i = 0; i < typeNames.length; i++) {Class<?> type = getProtoType(typeNames[i]);if (type == null) {try {type = Class.forName(typeNames[i]);} catch (ClassNotFoundException e) {e.printStackTrace();}}types[i] = type;}this.method = cls.getDeclaredMethod(field.getName(), types);this.method.setAccessible(true);}else {for (Method method : cls.getDeclaredMethods()) {if (method.getName().equals(field.getName())) {this.method = method;this.method.setAccessible(true);break;}}}if (this.method == null) {throw new NoSuchMethodException(field.getName());}
}
看起来很长的实现,实际上是对三种可能的情况做了区分处理:
1.@MethodParams
注解声明参数的情况
2.@MethodReflectParams
注解声明参数的情况
3.没有使用注解的情况,即无参的场景
然后照例,增加了一个便捷的调用方法call(Object receiver, Object... args)
。同样的,这里也没过多的校验,直接透传给实际的Method
实例。看下代码:
public T call(Object receiver, Object... args) {try {return (T) this.method.invoke(receiver, args);} catch (InvocationTargetException e) {if (e.getCause() != null) {e.getCause().printStackTrace();} else {e.printStackTrace();}} catch (Throwable e) {e.printStackTrace();}return null;
}
5. 小结
至此,也就把几个有代表性的反射结构分析了一遍。可以看到,声明里重要的信息都是通过RefClass
内的反射结构的Field定义提供的,反射结构在实例化的过程中,从中取出信息,做处理。这种用法,实在高明。
virtualapp-RefClass反射机制(转载)相关推荐
- Java的反射机制 工厂模式综合讲解【转载自51CTO】
2019独角兽企业重金招聘Python工程师标准>>> Java的反射机制 工厂模式综合讲解 1.什么叫反射 Java.lang.reflect包下 正常情况下我们可以通过类实例化一 ...
- (转载)Java反射机制
Java反射机制是Java语言被视为准动态语言的关键性质.Java反射机制的核心就是允许在运行时通过Java Reflection APIs来取得已知名字的class类的相关信息,动态地生成此类,并调 ...
- Golang反射机制的实现分析——reflect.Type方法查找和调用
在<Golang反射机制的实现分析--reflect.Type类型名称>一文中,我们分析了Golang获取类型基本信息的流程.本文将基于上述知识和经验,分析方法的查找和调用.(转载请指明出 ...
- Golang反射机制的实现分析——reflect.Type类型名称
现在越来越多的java.php或者python程序员转向了Golang.其中一个比较重要的原因是,它和C/C++一样,可以编译成机器码运行,这保证了执行的效率.在上述解释型语言中,它们都支持了&quo ...
- 利用java反射机制 读取配置文件 实现动态类载入以及动态类型转换
作者:54dabang 在spring的学习过程之中,我们能够看出通过配置文件来动态管理bean对象的优点(松耦合 能够让零散部分组成一个总体,而这些总体并不在意之间彼此的细节,从而达到了真正的物理上 ...
- 【教程】【FLEX】#004 反射机制
总结: 目前用到反射的主要有两个方法 1. getDefinitionByName //根据类名,返回对象(反射实例化对象) 2. describeType ...
- Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法...
Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法 www.MyException.Cn 发布于:2012-09-15 ...
- 利用Java反射机制和poi插件操作excel
最近在公司写一个利用poi插件读取excel的东西,,不想每一个都写一遍解析代码.就想利用Java的反射机制,写对应的实体类,获取对应excel中的值,下面是解析的代码,仅供参考.不足之处,望指出/* ...
- Java反射机制分析指南
一.JAVA是动态语言吗? 一般而言,说到动态言,都是指在程序运行时允许改变程序结构或者变量类型,从这个观点看,JAVA和C++一样,都不是动态语言. 但JAVA它却有着一个非常突出的动态相关机制:反 ...
- 反射 字段_详解面试中常考的 Java 反射机制
反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性和方法. 反射是一项高级 ...
最新文章
- 谷歌发布地图「时光机」:100年前,你家街道长啥样?
- php 验证微信token_微信token认证程序
- SqlServer2000 类似sqlserver2005的 rownumber() 函数
- 关于测试行业的零星思考
- 战双帕弥什显示服务器满员,战双帕弥什星火和信标服务器有何区别
- 14. 表单标签及其应用实例
- 【HDU - 2093】 考试排名(排序+格式输出)
- 朗读评价语言集锦_运用朗读评价语
- mysql 储存过程放到哪_MySQL储存过程
- WebSocket和WebRtc的一些心得
- 快捷方式全部变成LNK文件修复方法
- 统计学习方法——机器学习和统计学习
- Mysql按时间区段(每隔30分钟)统计数据并展示
- 一个出身寒门的状元之死全文【原文】
- 火狐浏览器,访问腾讯云服务器的时候,出现建立安全连接失败的问题。
- [Unity]利用Mesh在Unity中绘制扇形图片2
- 区块链学习者终极指南
- HTML制作法国国旗
- 温湿度雨雪复合传感器
- 华为5G时代最新战略出炉!【附报告下载】