java反射调用优化

856 阅读2分钟

反射就是绕开编译器,在运行期直接从虚拟机获取对象实例/访问对象成员变量/调用对象的成员函数。

一般反射可能底层框架用的比较多 业务开发很少会用到 不过最近在做一个参数配置映射的需求 里面因为上游可能传的对象不固定 所以需要用object接收 所以采用反射来处理 顺便研究了下里面的性能问题 目前其实也有高性能的放射第三方包ReflectASM觉得麻烦其实用这个现成的也可以

先说下反射耗时的点在哪里

  1. 获取Class实例:Class#forName()
  2. 获取Field实例:Class#getDeclaredField()、Class#getDeclaredFields()、Class#getField()、Class#getFields()。
  3. 获取Method实例:Class#getDeclaredMethod()、Class#getDeclaredMethods()、Class#getMethod()、Class#getMethods()。
  4. 获取Constructor实例:Class#getDeclaredConstructor()、Class#getDeclaredConstructors()、Class#getConstructor()、Class#getConstructors()。

可以看到 耗方法的基本都是因为获取对象的各个实例 之所以会这样 以getMethod方法为例当调用这个方法进行获取实例的时候是需要从不连续的堆中检索代码段、定位函数入口,获得了函数入口 导致性能损耗

确定了性能是在获取这些实例上以后 就可以考虑怎么进行优化 目前我这边是通过把实例缓存起来

下面是普通的通过反射获取变量名和变量值 目前本地电脑测试 循环一百万次 差不多6百毫秒左右

    private static void test(Object objec){
        Field[] declaredFields = objec.getClass().getDeclaredFields();
        for (Field field : declaredFields){
            String name = field.getName();
            try {
                field.setAccessible(true);
                Object o = field.get(objec);
            }catch (Exception e){
                System.out.println("异常" + JSON.toJSONString(e));

            }
        }
    }

下面的代码则是把Field[]缓存起来 第一次请求的时候map为空 则会放到map中 后面请求都可以直接从map中获取 也是循环一百万次 差不多1百多毫秒 相当于性能快了6倍

    private static void test1(Object objec,HashMap<String,Field[]> setterMap){
        Field[] declaredFields = setterMap.get(objec.getClass().getName());
        if (declaredFields == null){
            declaredFields = setterMap.put(objec.getClass().getName(),objec.getClass().getDeclaredFields());
            declaredFields = setterMap.get(objec.getClass().getName());
        }

        for (Field field : declaredFields){
            String name = field.getName();
            try {
                field.setAccessible(true);
                Object o = field.get(objec);
            }catch (Exception e){
                System.out.println("异常" + JSON.toJSONString(e));

            }
        }
    }

上面的代码主要是通过反射获取对象的值 其他的如初始化实例 反射调用某个方法名其实也是用的相同的方法 就是把实例缓存起来 目前测试如果缓存了method实例和没有缓存性能差了2倍

    private static void test2(Object objec,HashMap setterMap,HashMap methodMap){
        try {
            Method method = (Method)methodMap.get(objec.getClass().getName());
            if (method == null){
                methodMap.put(objec.getClass().getName(), objec.getClass().getMethod("getType"));
                method = (Method)methodMap.get(objec.getClass().getName());
            }
            Field[] declaredFields = (Field[])setterMap.get(objec.getClass().getName());
            if (declaredFields == null){
                setterMap.put(objec.getClass().getName(),objec.getClass().getDeclaredFields());
                declaredFields = objec.getClass().getDeclaredFields();
            }

            for (Field field : declaredFields){
                String name = field.getName();
                try {
                    field.setAccessible(true);
                    Object invoke = method.invoke(objec);
                }catch (Exception e){
                    System.out.println("异常" + JSON.toJSONString(e));

                }
            }
        }catch (Exception e){
            System.out.println("异常" + JSON.toJSONString(e));
        }

    }