天天看点

向下兼容:hessian序列化时增加属性,反序列化会报错吗?

对于成熟期的公司,在业务发展到一定阶段,会面临向下兼容的问题。而这个问题多体现在接口上,更具体的是,RPC接口的向下兼容的问题。

比如,服务端提供一个类,这个类“升级”的时候,增加了一些属性和方法,而消费端还没有同步升级依赖的库,即反序列化这个类的时候,并没有增加的属性,此时会怎样,会报错吗?即Hessian反序列化时,类的属性与序列化时不一致会怎样?

想要知道这个必须明白Hessian的序列化原理:

综述:Hessian 是由 caucho 提供的一个远程通讯library,将对象序列化成二进制进行网络传输,使用Http协议进行网络传输。实际上,可以简单理解为Hessian通过web server容器(比如servlet等)建立了一个Server,在使用的Hessian的时候,就相当于服务端将任何一个Java类暴露给了一个HessianServlet,而客户端hessian client调用servlet,获得远程方法的输出结果。

所以,了解hessian,总体需要了解三点:

1. 网络传输:hessian的交互核心数据,比如“调用的方法”和参数列表信息,将通过post请求的body体直接发送,格式为字节流。hessian将辅助信息封装在http header中,比如“授权token”、“meta数据”等。因网络传输完全基于http协议,无需赘言。需要注意的是,Hessian的client每次调用,都会开启一个新的http链接,所以各个调用之间无法共享数据。

2. 服务连接:通过HessianServlet类实现了一个Java Servlet,每个服务通过这个实例来发送/接收请求。HessianServlet类的初始化参数是远程接口的实现类的全类名。

3. 序列化/反序列化:因为hessian的接口调用基于http,且以字节码的方式进行数据交换,那么hessian需要提供自定义的“protocol”以及序列化/反序列机制。一个好消息是,Hessian中使用的反射机制/序列化机制/动态代理都是基于java原生API,减少了我们学习的负担~~

一次完整的远程调用过程:

1. 首先,HessianProxyFactory类通过url维护跟服务端的远程连接,并生成代理类(Java Proxy实例)。当调用远程方法的时候,HessianProxy实例序列化"方法名"、"参数列表"等,通过HessianOutput类将序列化的字节数据,按照协议,写入inputStream,并通过HttpURLConnection发送给远端。

Hessian对一个请求序列化的后的“字节码成帧”格式是:

[“方法名称“的字节长度]["方法名称”字节序列][参数的个数]{[参数类型][”参数“的字节长度][”参数“的字节序列]...

比如sayHello(String message)方法,那么序列化的字节流格式可能为:

[8][sayHello][1]['S'][5]['hello']  
//其中“8”表示“sayHello”方法名称为8个字节  
//"1"表示参数的个数为1  
//“S”表示参数的类型为“String”,hessian中定义了大量的简写字母,用来表示java数据类型  
//“5”表示参数的字节个数为5  
//"hello"表示此参数的值为“hello”,不过实际上传输的应该是“hello”对应的字节序列。  
           

2. 服务端,每个HessianServlet都会持有一个HessianSkeleton实例,这个实例持有“service-class”对象,并在初始化时通过反射机制将“service-class”的所有方法列表放在一个methodMap中,key为“方法名”,value为Method(java.lang.reflect.Method类),同时为了避免方法重载,还会额外的将“方法名_参数类型”作为一个新key,也放入methodMap中。当Http请求到达Servlet时,HessianSkeleton实例负责从请求中解析出“方法名”和参数列表,然后根据方法名,从methodMap中获取Method,并调用其invode方法。

例如,当上面的请求到达后,服务端根据“字节码成帧”逐个读取信息,首先读取一个32位的int,得到“8”,然后读取8个字节并使用utf8的方式编码成String字符串,将获得“sayHello”,此时server端已经可以知道client需要调用的方法名称为“sayHello”;然后对于一个32为的int,得到1,表示参数列表的个数为1;然后在读取一个字节,获得“S”,表示参数为String类型.....服务端将执行结果的对象,进行序列化,用一样的方法传给客户端。

3. 最后,客户端将服务端返回的结果进行反序列化,将对象所有属性取出来,首先根据反序列化类型,存放在一个map中,key = 属性名,value是反序列类,然后再new一个Object,并逐个属性进行赋值。

所以,回到我们最初的问题,因为中间有一个Map机制,在服务端增加属性、方法的时候,不管是在已有属性的前面、后面、中间增加,当客户端使用较少的字段进行反序列化,是不会报错的,就好像那些新增的属性、方法根本不存在。