在上一篇文章中向大家介绍了什么是声明式反射,以及具体使用方式与常用写法的对比。那么本篇文章将向大家介绍Reflex框架的结构以及原理。
Reflex架构
废话不多说,直接上图:![](https://img.haomeiwen.com/i24808764/65eb45037ac74e25.png)
从上图可以看出Reflex是由核心的ReflexClass
与围绕在其周围的Field、StaticField、Method 三部分构成。这三部分是用于声明和使用反射的结构的定义。从名字也能看出该反射结构的类型信息,如构造方法、数据类型、是否静态等。
在Method模块右边的MethodParams、MethodReflexParams则是用于声明函数参数类型的注解,函数相关的反射结构的定义会需要用到它。它们两个的差别在于,MethodParams接受的数据类型是Class<?>,而MethodReflexParams接受的数据类型是字符串,对应类型的全描述符,如java.util.HashMap。MethodReflexParams主要是用于一些JDK或SDK没有暴露出来的,无法直接访问到的类。
工作流程
class ReflexDemoMapping {
public static ReflexInt mId;
public static ReflexStaticObject<String> TAG;
public static ReflexMethod<Void> doSomething;
@MethodParams(value = {String.class, String.class})
public static ReflexStaticMethod<Void> printLog;
static {
ReflexClass.load(ReflexDemoMapping.class, ReflexDemo.class);
}
}
参考之前的代码,ReflexClass
是整个声明中最外层的结构。这整个框架要能运作,也需要从这里开始,逐层向里地初始化。通过Static静态块的方式,使框架具备了按需加载的特性。
接下来我们来看一下ReflexClass.load(Class<?> mappingClass, Class<?> realClass)内部的逻辑。
public static Class<?> load(Class<?> mappingClass, Class<?> realClass) {
// 获取所有声明的变量
Field[] fields = mappingClass.getDeclaredFields();
for (Field field : fields) {
try {
// 检查变量是否被修饰为静态变量
if (Modifier.isStatic(field.getModifiers())) {
// 核心 从REFLEX_TYPES中查找变量对应的反射结构
Constructor<?> constructor = REFLEX_TYPES.get(field.getType());
if (null == constructor) {
continue;
}
// 核心 调用反射结构的构造函数并为映射表中的字段赋值
field.set(null, constructor.newInstance(realClass, field));
}
} catch (Exception ignore) {
}
}
return realClass;
}
REFLEX_TYPES
装载了所有Reflex支持的反射结构类型。
private static HashMap<Class<?>, Constructor<?>> REFLEX_TYPES = new HashMap<>();
static {
REFLEX_TYPES.put(ReflexObject.class, ReflexObject.class.getConstructor(Class.class, Field.class));
...// 其他类型
REFLEX_TYPES.put(ReflexStaticObject.class,
...// 其他静态类型
REFLEX_TYPES.put(ReflexMethod.class, ReflexMethod.class.getConstructor(Class.class, Field.class));
... // 其他函数类型
}
总结一下:load函数主要处理了在mappingClass中查找需要初始化的反射结构(如 public static ReflexInt mId),其限制查找范围为静态成员变量及类型必须为Reflex支持的类型,并为找到的结构变量赋值。
Field反射结构
对于每一种反射结构而言,最关心的其实只有一个问题:映射表中声明的变量对应反射目标类中的哪个字段或函数?
为了解决这个问题,选择了强制映射类中声明的变量必须与实际类中的变量或函数名保持一致,这样既避免了额外的参数又使得映射类的可读性更强,每行的信息密度更高。
在解决了这个问题之后,我们参考ReflexByte
看一下反射结构的内部。
abstract class BaseField {
protected Field mField;
public BaseField(Class<?> cls, Field field) throws NoSuchFieldException {
mField = cls.getDeclaredField(field.getName());
mField.setAccessible(true);
}
}
在父类中根据映射表中的字段名称去实际类中查找对应字段并修改其可访问性。
public final class ReflexByte extends BaseField{
public ReflexByte(Class<?> cls, Field field) throws NoSuchFieldException {
super(cls, field);
}
public byte get(Object object) {
try {
return mField.getByte(object);
} catch (Exception ignore) {
return (byte) 0;
}
}
public void set(Object obj, byte value) {
try {
mField.setByte(obj, value);
} catch (Exception ignore) {
}
}
}
因为每个反射结构代表的含义都不一样,故将每种类型的操作逻辑分解每一个反射机构当中,使得其对对应类型的操作与处理更加内聚。
接下来再看一下静态变量结构的内部实现:
abstract class BaseStaticField extends BaseField{
public BaseStaticField(Class<?> cls, Field field) throws NoSuchFieldException {
super(cls, field);
checkIsStatic(cls, mField);
}
protected void checkIsStatic(Class<?> cls, Field field){
if (!Modifier.isStatic(field.getModifiers())) {
throw new NotStaticException(cls, field);
}
}
}
一如既往的仔父类中进行变量的查找与访问性修改,唯一不同的是静态结构中增加了静态类型的检测。
public final class ReflexStaticByte extends BaseStaticField {
public ReflexStaticByte(Class<?> cls, Field field) throws NoSuchFieldException {
super(cls, field);
}
public byte get() {
try {
return mField.getByte(null);
} catch (Exception ignore) {
return (byte) 0;
}
}
public void set(byte value) {
try {
mField.setByte(null, value);
} catch (Exception ignore) {
}
}
}
由于静态变量在反射时不需要传入目标对象就可对其进行操作,所以在此处简化了get、set函数。其他类型的反射结构也与此大同小异,只有内部对不同类型的操作有细微差别,在此不再累述。
Method 反射机构
与变量的反射机构不同,函数的反射机构需要关注三点:
- 函数名
- 函数入参
- 函数出参
为了让使用更加简单,函数名可以通过映射类中变量的名称获得、函数入参通过使用 @MethodParams
或@MethodReflexParams
标注在映射表的变量上来声明,出参则通过泛型<RESULT>
定义。
protected Method mMethod;
public BaseMethod(Class<?> cls, Field field) throws NoSuchMethodException {
if (field.isAnnotationPresent(MethodParams.class)) {
// 处理普通类型参数
mMethod = handleWithParams(cls, field);
} else if (field.isAnnotationPresent(MethodReflexParams.class)) {
// 处理全描述符参数,此时的参数类型也被视为反射某一具体结构
mMethod = handleWithReflexParams(cls, field);
} else {
// 处理无参函数
mMethod = handleWithNoParams(cls, field);
}
if (null == mMethod) {
throw new NoSuchMethodException("Not find [" + field.getName() + "] function in [" + cls.getName() + "]");
}
mMethod.setAccessible(true);
}
并提供了简单的函数操作
@SuppressWarnings("unchecked")
public RESULT call(Object receiver, Object... args) {
try {
return (RESULT) mMethod.invoke(receiver, args);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
在函数执行时并没有过多的校验,也没有检查返回值与泛型是否一致。如果返回值与声明的泛型不一致则认为是映射类声明时出现错误,应由开发者修改,而不应该由框架拦截检查。
总结
Reflex并没有太过复杂的流程,其主要是运用了JVM对于静态变量与静态块的加载机制以及单一原则、开闭原则、迪米尔原则和模板模式来运作。通过简单的封装,使得反射API完全内聚,使用者只需要关注定义与声明映射关系,通过一张张映射表更清晰的传达要做的操作,提升单行代码的信息含量。
GitHub:项目源码
优雅永不过时-卡密尔
网友评论