如何优雅的使用反射(一)

  相信每一位接触过Java的同学对于反射都不陌生。作为一种从更高维度操纵代码的方式,反射通常被用于实现Java上的Hook技术。
  然而反射的使用方式也不难,随便一Google 就有一堆文章。所以本文将不再累述反射的Api与原理等概念,而是从使用者的角度出发,站在Coder的角度上去观察如何优雅的写出反射代码。

常见的反射写法

举个栗子.jpg
public class ReflexDemo {
    private int mId = 0xff;
    private static final String TAG = "Reflex";
    private void doSomething(){
        printLog(TAG,"I will do something!");
    }

    private static void printLog(String tag,String message){
        Log.i(tag,message);
    }
}

  如上面代码中的ReflexDemo,包含了私有变量、私有静态变量、私有函数、私有静态函数。如果我们想访问他们就必须通过反射调用,所以有了如下代码。

 try {
            ReflexDemo reflexDemo = new ReflexDemo();
            Field fieldName = ReflexDemo.class.getDeclaredField("mId");
            fieldName.setAccessible(true);
            fieldName.set(reflexDemo, 0xff >> 2);
            Method doSomething = ReflexDemo.class.getDeclaredMethod("doSomething");
            doSomething.setAccessible(true);
            doSomething.invoke(reflexDemo);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

简单观测我们可以发现普通的反射写法存在以下缺点:

  1. 上面的代码就是我们经常使用反射时的写法。很显然为了达到Hook的目的 我们需要先获取对应的变量/函数,再调用setAccessible修改其是否禁用安全检查,最后才能达到我们的目的修改内容/调用函数。这样使用起来会比较繁琐。
  2. 每调用一个变量或函数都需要将涉及到的反射接口写一遍。

  虽然以上两点在少量使用时并不会让人觉得有什么问题,但对于一些大型项目且比较依赖使用反射来实现核心功能的项目,以上两点就会成为噩梦般的存在。

目标

想要优化使用反射的过程,需要遵循以下几个原则:

  1. 越简单越好,在实现功能的基础上代码应该更容易让人阅读,复杂的东西不容易理解且不利于使用。
  2. 尽可能的复用,不要重复性的写无意义的代码。
  3. 轻量快捷,使用起来最好让人一目了然。

声明式反射

什么是声明式反射?相信这是大家的第一反应,那么我们先来看一下声明式反射的使用方法。


退后我要开始装B了.jpg
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);
    }
}

ReflexDemoMapping就是ReflexDemo的反射声明类。为了更直观的观察两者间的映射关系我们看下表:

声明 声明类型 映射类型 映射对象
mId ReflexInt int ReflexDemo.mId
TAG ReflexStaticObject String ReflexDemo.TAG
doSomething ReflexMethod Method ReflexDemo.doSomething()
printLog ReflexStaticMethod Method ReflexDemo.printLog()

声明式:用类定义的方式,直截了当的定义出来反射哪个类、哪个成员对象、哪个函数。只关注定义,不关注实现细节与赋值过程

  对比一下我们可以发现 ReflexDemoMapping的每一个成员变量与要反射调用的变量或函数名一一对应,这样做的好处就是再后期维护时可以更加简单快捷的还原意图,降低维护成本。

  接下来请看静态块中的内容,我们知道JVM在类的初始化阶段会完成对静态变量的初始化,静态块执行等工作,所以保证了反射声明是按需加载的。而静态块中执行的ReflexClass.load(Class mapping,Class real)函数就是Reflex框架的真正初始化入口。

使用起来也是非常的简洁明了:

        ReflexDemo reflexDemo = new ReflexDemo();
        ReflexDemoMapping.mId.set(reflexDemo, 99999);
        ReflexDemoMapping.doSomething.call(reflexDemo);
        ReflexDemoMapping.printLog.call(ReflexDemoMapping.TAG.get(), "Id is :" + ReflexDemoMapping.mId.get(reflexDemo));
}

  至此相信大家已经对声明式反射有了一些了解,相比传统的反射调用,声明式的使用更加简单,在明确了意图的同时也减少了胶水代码。在声明里,完全看不到任何和反射API相关的代码,基本隐藏了Java的反射框架,对使用者来说,几乎是无感的。

Reflex

   Reflex 提供了Java的八大基础类型用于反射声明,分别为ReflexByteReflexShortReflexIntReflexLongReflexFloatReflexDoubleReflexBooleanReflexChar,并提供了ReflexObject用于对其它类型的支持。

   同时还提供了用于反射静态变量的声明类型ReflexStaticByteReflexStaticShortReflexStaticIntReflexStaticLongReflexStaticFloatReflexStaticDoubleReflexStaticBooleanReflexStaticCharReflexStaticObject

  用于声明反射函数的 ReflexMethodReflexStaticMethodReflexConstructor

  用于标注函数参数的 MethodParamsMethodReflexParams

  ReflexClass作为整个框架的入口,其承担了构造反射类与初始化映射表的重担。并对外提供了load(Class<?> mappingClass, String realClassName)load(Class<?> mappingClass, Class<?> realClass) 两个函数。mappingClass为反射映射表,realClass为反射操作的目标类,可以传入ClassPackageName+ClassName

   框架要求MappingClass 反射映射表中的变量必须为静态变量且变量名要与要反射的变量或函数一致

引入方式

Gradle:

  allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }

并在需要引入的Module 中添加:

    dependencies {
         implementation 'com.github.CoderAlee:Reflex:1.0.0'
 }

Maven

<repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

添加依赖:

    <dependency>
        <groupId>com.github.CoderAlee</groupId>
        <artifactId>Reflex</artifactId>
        <version>1.0.0</version>
    </dependency>

框架结构及原理

传送门:如何优雅的使用反射(二)

来不及了快上车.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342