美文网首页Android开发程序员Android知识
反射相关知识及jOOR反射库介绍

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

作者: JarryWell | 来源:发表于2017-11-25 22:18 被阅读445次

    反射是什么

    反射(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

    相关文章

      网友评论

        本文标题:反射相关知识及jOOR反射库介绍

        本文链接:https://www.haomeiwen.com/subject/juoqbxtx.html