反射相关知识及jOOR反射库介绍

反射是什么

反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

Oracle官方对反射的解释:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。

使用反射有如下优缺点

优点:

  1. 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性
  2. android的开发中,反射使得在版本和机型的适配兼容性上提供了大的方便

缺点:

  1. 使用反射的性能较低
  2. 使用反射相对来说不安全(另外,对于混淆类的情况很可能对应不上)
  3. 破坏了类的封装性(可以通过反射获取这个类的私有方法和属性 )

因此,反射有利有弊,需要分具体场景来择取是否真的需要反射!!!

java虚拟机实例化一个对象的流程描述:

假如你写了一段代码:Object o = new Object(); 运行了起来!
其内部运行流程为如下描述:
首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

以上流程来自于这里的描述: https://www.zhihu.com/question/24304289

JDK反射介绍

在这里先看一下sun为我们提供了哪些操作反射的类:

java.lang.Class; //类对象
java.lang.reflect.Constructor; //构造器对象
java.lang.reflect.Field; //属性对象
java.lang.reflect.Method; //方法对象
java.lang.reflect.Modifier; //修饰符对象

注:Modifier可用来描述abstractfinalinterfacenativeprivateprotectedpublicstaticstrictfpsynchronizedtransientvolatile等。

JDK反射具体有如下用法:

注意区分带不带Declared字样的方法名区别:

  • 带有Declared时表示获取与publicprivateprotect修饰无关的所有对象,但不能是继承来的
  • 不带Declared时,表示只获取public标识符修饰的对象,且还包括从超类继承来的public对象
  1. 获取对象构造器
//获得指定参数类型params的public构造函数
Constructor getConstructor(Class[] params);

//获得类的所有public构造函数
Constructor[] getConstructors(); 

//获得使用指定参数类型params的构造函数
Constructor getDeclaredConstructor(Class[] params);

//获得类的所有的构造函数
Constructor[] getDeclaredConstructors();
  1. 获取类属性字段
//获得类中命名为name的public字段
Field getField(String name);

//获得类的所有public字段
Field[] getFields();

//获得类中命名为name的字段
Field getDeclaredField(String name);

//获得类中的所有的字段
Field[] getDeclaredFields();
  1. 获取类方法
//使用指定参数类型params,获得名称为name的public方法
Method getMethod(String name, Class[] params);

//获得类中所有的public方法
Method[] getMethods();

//使用指定参数类型params,获得命名为name的方法
Method getDeclaredMethod(String name, Class[] params);

//获取类中的所有方法
Method[] getDeclaredMethods();

android开发中常见用法如下(其他用法不再进行测试描述,具体可以参见GH-Demo):

public static int getStatusBarHeight(Context context) {
    try {
         //构造dimen类对象
        Class<?> c = Class.forName("com.android.internal.R$dimen");
        Object obj = c.newInstance(); //使用无参构造函数实例化dimen对象
        Field field = c.getField("status_bar_height"); //获取obj对象中名称为status_bar_height的Field字段
        field.setAccessible(true); //修改访问修饰属性(假设为private修饰)
        int height = Integer.parseInt(field.get(obj).toString()); //从obj对象中读取field字段对应的value
        return context.getResources().getDimensionPixelSize(height);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return 0;
}

下面介绍一下网上比较热门且自认为用法比较灵活的java反射封装类库:jOOR

jOOR官方介绍

jOOR - Fluent Reflection in Java jOOR is a very simple fluent API that gives access to your Java Class structures in a more intuitive way. The JDK’s reflection APIs are hard and verbose to use. Other languages have much simpler constructs to access type meta information at runtime. Let us make Java reflection better. http://www.jooq.org/products

大意为:jOOR提供了一种更为直观的方式来构建JDK原生的反射调用,因为JDK提供的反射API使用起来较冗长(它对包java.lang.reflect进行了简单封装,使得反射更加方便)。

gradle配置为:compile'org.jooq:joor:0.9.6'

先看一个简单例子(同上述方法getStatusBarHeight()),对比JDK的反射API和jOOR的方法调用。

public static int getStatusBarHeight(Context context) {
    final int statusHeightId = Reflect.on("com.android.internal.R$dimen")
            .create().field("status_bar_height").get();
    return context.getResources().getDimensionPixelSize(statusHeightId);
}

从上面的例子就可以看到jOOR明显的优势:

  1. 简化了反射冗长的异常处理。
  2. 简化了对ClassMethod等反射类的实例化,改为统一Reflect替换。
  3. 支持private方法的调用,内部动态区分是否需要accessible()
  4. 将反射调用封装为更流行的链式调用方式,代码更容易理解(类似Rxjava的封装方式)。

jOOR功能介绍

  1. 提供on()操作符对Class、对象(还包括方法、构造函数)进行统一实例化为Reflect对象,后续所有的反射操作基于该Reflect对象进行。
  2. 调用方式均被封装成返回Reflect对象的链式结构,在使用上使得代码更加简洁。
  3. 对方法的签名匹配封装了更完善的匹配规则,包括精确匹配exactMethod()、近似匹配similarMethod()【对函数参数的近似匹配(int -> Integer)】和基类搜索等。
  4. 调用私有方法的不需要显示调用setAccessible(),内部动态读取修饰标记自动适配。
  5. 更加简洁的实现了对象构造函数的反射调用create()方法。
  6. 函数的调用call()方法组合成了可以拼接在Reflect的对象后面的链式方法。
  7. 额外增加了高级操作符as(),它实现了类的代理访问以及POJO对象get/set/is方法实现。

jOOR API介绍

  1. 通过类名转换成一个Reflect对象
public static Reflect on(String name);
public static Reflect on(String name, ClassLoader classLoader);
public static Reflect on(Class<?> clazz);
  1. 将一个对象转换成一个Reflect对象
public static Reflect on(Object object);
  1. 修改一个AccessibleObject类型的对象的访问权限
public static <T extends AccessibleObject> T accessible(T accessible);
  1. 返回Reflect对象具体包装的类型,类型为Class或者对象,具体由操作符on()的重载参数决定
public <T> T get();
  1. name指定的field转换成一个Reflect对象,后续对field的操作变为对Reflect对象的操作
public Reflect field(String name);
  1. 返回当前Reflect对象的所有field属性,并转换成Reflect对象的map
public Map<String, Reflect> fields();
  1. 修改(获取)field属性值
public Reflect set(String name, Object value);
public <T> T get(String name);
  1. 反射调用name指定的函数,此函数封装了对函数的签名的精准匹配和近似匹配
public Reflect call(String name);
public Reflect call(String name, Object... args);
  1. 反射调用指定类的构造函数,封装了精准匹配和近似匹配
public Reflect create();
public Reflect create(Object... args);
  1. 返回当前Reflect对象封装的对象类型
public Class<?> type();
  1. 给封装对象创建一个代理访问,还实现了对POJO对象setXX/getXX/isxx功能(此为Reflect对象的高级功能)
public <P> P as(Class<P> proxyType);

jOOR 典型用法

先列出测试举例TestField类定义以便有个大概的概念:
声明实例:TestField testField = new TestField();

public static class TestField {
    /**
     * private
     */
    private int INT1 = new Integer(0);
    private Integer INT2 = new Integer(2);


    /**
     * private & static
     */
    private static int S_INT1 = new Integer(3);
    private static Integer S_INT2 = new Integer(0);

    /**
     * private & final
     */

    private final int F_INT3 = new Integer(4);

    /**
     * object
     */
    private TestField I_DATA;


    public void tetProxy(String message) {
        Log.d("TestJOOR", "tetProxy: " + message);
    }
}

代理接口POJOInterface的定义:

public interface POJOInterface {

    String substring(int beginIndex, int endIndex);

    void tetProxy(String message);

    void setFoo(String foo);

    String getFoo();

    void setBaz(String baz);

    String getBaz();
}

POJO对象的Map实现:
构造实例 Map<String, Object> map = new TestBean();

private class TestBean extends HashMap<String, Object> {

    private String baz;

    public void setBaz(String baz) {
        this.baz = "POJO-MyMap: " + baz;
    }

    public String getBaz() {
        return baz;
    }
}
  1. 调用String的非静态方法(通过实例对象构造Reflect
String value = "1234";
String subString  = Reflect.on((Object) value).call("substring", 0, 2).get();
//结果为:subString = "123";
  1. 调用String的静态方法(通过String.class类名构造Reflect
String valueOf = Reflect.on(String.class).call("valueOf", true).get();
//结果为:valueOf = "true";
  1. 修改private属性
int init_int = Reflect.on(testField).get("INT2"); //init_int=2;
Reflect setReflect = Reflect.on(testField).set("INT2", 300);
int result = setReflect.field("INT2").get(); //result = 300;
  1. 修改static属性
int sInit_int = Reflect.on(TestField.class).get("S_INT1"); //sInit_int = 3;
Reflect obj = Reflect.on("com.android.test.joor.TestJOOR$TestField").set("S_INT2", 500);
int sInt2 = obj.field("S_INT2").get(); //sInt2 = 500;
  1. 复杂链式修改多个属性值
Reflect.on(testField).set("I_DATA", Reflect.on(TestField.class).create())
            .field("I_DATA").set("INT1", 700).set("INT2", 800);
  1. interface实现类对象的代理
String asResult = Reflect.on((Object) "abc").as(POJOInterface.class).substring(1, 2);
//测试结果: asResult = "bc";

Reflect.on(testField).as(POJOInterface.class).tetProxy("this is proxy test!!");
//测试结果:调用TestField类中的tetProxy()函数。
  1. 对于没有实现set/get方法的Bean对象,对应的数据有以key-valuekeysetXx()xx后缀)的形式存放在HashMap
Reflect.on(map).as(POJOInterface.class).setFoo("abc");
int size = map.size(); //size = 1;
String value1 = (String) map.get("foo"); //value1 = "abc";
String value2 = Reflect.on(map).as(POJOInterface.class).getFoo(); //value2 = "abc";
  1. 已经实现了set/get方法的Bean对象,对应数据存放在Bean对象的字段中
Reflect.on(map).as(POJOInterface.class).setBaz("baz");
int size = map.size(); //size = 0; 没有存放在hasMap中
String value4 = (String) map.get("baz"); //value4 = null;
String value5 = Reflect.on(map).as(POJOInterface.class).getBaz(); //value5 = "MyMap: baz-test";

备注:
jOOR对反射的原始异常进行了转义---ReflectException,其直接基类为RuntimeException,这里意味着通过jOOR库的api开发时,不会强制开发人员用try...catch括起来,但一旦发生异常,程序就会中断运行

反射调用效率优化

从应用层面看,想对反射的执行效率做提升优化,只能在项目中明令禁止使用反射了,如果不可规避(一般避免不了),只能最大限度的降低反射的调用次数了。一般的做法是将第一次获取的ClassMethodField对象进行缓存起来,下次调用同样的反射对象时直接取已缓存对象进行相应调用。这里引用另一个反射调用的封装库 reflect,其内部就是将ClassMethodField等对象进行缓存以备下次调用,但个人认为其用法不如jOOR灵活,但优化思路值得参考。
本人参考reflect库的优化思路对jOOR内部进行修改,在不破坏外部调用接口的前提下对内部的ClassConstructorMethodField进行缓存。地址为GH-Demo-Reflect.java

参考文档

https://www.zhihu.com/question/24304289
https://github.com/masonTool/reflect

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