怎么理解序列化中的反射

发布时间:2021-10-21 13:43:58 作者:iii
来源:亿速云 阅读:179

本篇内容主要讲解“怎么理解序列化中的反射”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解序列化中的反射”吧!

前言

序列化大家都不陌生,说白了就是把当前类对象的状态保存为二进制,然后被用来持久化或者网络传输;常用的RPC框架在数据传输前都会进行序列化操作,主流的RPC框架包含了多种序列化方式比如protobuf,fastjson,kryo,hessian,java内置序列化等等,大致可以分为二进制和字符串(json字符串)。

反射

因为需要把当前类对象状态保存为二进制,所以往往需要获取所有类属性,这时候大部分的序列化方式都用到了反射,通过反射获取所有类属性获取方法,然后获取到属性值,大致如下:

//1.方法Method[] methods = obj.getClass().getDeclaredMethods();for(Method method : methods) {
    method.invoke(obj);
}//2.字段Field fields[] = obj.getClass().getDeclaredFields();for (Field field : fields) {
    field.get(obj);
}

但是反射往往在性能上被大家所怀疑,所以出现了类似protobuf采用自动生成序列化代码的方式,fastjson使用ASM代替反射的方式;下面我们先用简单的测试来对比一下各种方式的性能,看反射是否真的慢;

性能测试

在windows10+jdk8环境下分别对直接,反射,以及ASM调用方法分别进行压力测试,看起消耗的时间,测试中可以多次执行,取稳定的值;以下测试分别从Person对象通过方法获取属性值,如下:

public class Person {private String id;private String name;    public String getId() {return id;
    }public String getName() {return name;
    }
}

直接调用

直接调用也就是我们平时最常用的方式,直接通过对象调用方法名称获取属性值,我们在压测的时候会分别轮询两个方法:

public static void test() {
    Person person = new Person("10001", "zhaohui");long startTime = System.currentTimeMillis();for (int i = 0; i < 1_0000_0000; i++) {if (i % 2 == 0) {
            person.getId();
        } else {
            person.getName();
        }
    }long endTime = System.currentTimeMillis();
    System.out.println("Manual time:" + (endTime - startTime) + "ms");
    }

多次测试结果大概在90ms左右,直接调用速度是最快的,但是需要我们手动的写每个bean的序列化代码,或者像protobuf一样使用工具给我们生成所有的序列化代码,比如生成Person的序列化代码:

 public void writeTo(com.google.protobuf.CodedOutputStream output)throws java.io.IOException {getSerializedSize();if (((bitField0_ & 0x00000001) == 0x00000001)) {      output.writeInt32(1, id_);
    }if (((bitField0_ & 0x00000002) == 0x00000002)) {      output.writeBytes(2, getNameBytes());
    }getUnknownFields().writeTo(output);
 }

可以看到每个生成的bean都自动生成了序列化代码,并且所有的bean都继承于统一的抽象类,这样提供一整套规范;有个缺点就是每次修改需要手动改proto文件,然后重新生成代码;

反射调用

使用jdk提供的反射机制,获取Methods,然后获取属性值,具体代码如下:

    public static void test() throws Exception {long startTime = System.currentTimeMillis();
        Person person = new Person("10001", "zhaohui");
        Method[] ms = Person.class.getDeclaredMethods();for (int i = 0; i < 1_0000_0000; i++) {
            ms[i & ms.length - 1].invoke(person);
        }long endTime = System.currentTimeMillis();
        System.out.println("Reflex time:" + (endTime - startTime) + "ms");
    }

经测试时间大概维持在205ms左右,和直接调用还是存在一定差距的,不过jdk每一轮的升级,都在提升性能,比如jdk7中引入的MethodHandle,模拟字节码层面的调用;

ASM调用

反射是读取持久堆上存储的类信息,而ASM是直接处理.class字节码的,无需加载类,我们这里使用ReflectASM来进行测试;

ReflectASM 是一个非常小的 Java 类库,通过代码生成来提供高性能的反射处理,自动为 get/set 字段提供访问类,访问类使用字节码操作而不是 Java 的反射技术,因此非常快。
    public static void test() {
        Person person = new Person("10001", "zhaohui");long startTime = System.currentTimeMillis();

        MethodAccess methodAccess = MethodAccess.get(Person.class);
        String[] mns = methodAccess.getMethodNames();int len = mns.length;int indexs[] = new int[len];for (int i = 0; i < len; i++) {
            indexs[i] = methodAccess.getIndex(mns[i]);
        }for (int i = 0; i < 1_0000_0000; i++) {
            methodAccess.invoke(person, indexs[i & len - 1]);
        }long endTime = System.currentTimeMillis();
        System.out.println("ASM time:" + (endTime - startTime) + "ms");
    }

经测试时间维持在110ms左右,速度还是很快的,快赶上直接调用了;其中为了获得最大性能,应使用方法或字段索引而不是名称;

总结

可以看到虽然反射性能一直在提升,但是相比直接调用和ASM的方式还是有一点差距;但其实如果用在RPC上这点时间在整个网络传输上来说可以说微乎其微;如果对性能极度追求,可以考虑使用直接调用或者ASM的方式;

思考

关于直接调用上面说到protobuf,通过工具生成序列化代码,但是这种方式每次改动都要手动生成代码,有点麻烦,是否可以直接利用lombok这种框架做一个扩展,自动生成序列化代码,其实lombok底层也用到ASM,直接生成字节码代码,提供序列化注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.SOURCE)
public @interface Serialize {
}

然后可以直接把注解应用到bean中,直接帮助我们生成序列化代码,就像@Getter/@Setter一样;相当于直接调用和ASM方式的一种整合;类似如下代码:

@Serializepublic class Person {private String id;private String name;    //自动生成public byte[] serialize(){
        ByteBuffer bb = ByteBuffer.allocate(100);
        bb.put(id.getBytes());
        bb.put(name.getBytes());return bb.array();
    }
}

到此,相信大家对“怎么理解序列化中的反射”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

推荐阅读:
  1. 理解golang反射(reflection in Go)
  2. golang反射使用场景——对象的序列化

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jdk

上一篇:iostat命令怎么用

下一篇:linux命令格式是怎么样的

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》