前面的文章已经说了Hessian的一些基本信息,下面就通过一下基本实例由浅入深地看看Hessian的API如何使用。实例主要从两个方面进行介绍,首先我们需要看看Hessian的序列化功能,然后就是Hessian的RPC功能,因为RPC功能建立在序列化功能之上,因此我们看看序列化的功能;
- 序列化primitive type
序列化primitive type虽然非常简单,但是我们需要了解Hessian的一些压缩功能,因为在序列化一个数据时需要记录两方法的信息:类型与数据,为了使得序列化的总数据尽量小,必须对常用的primitive type的类型进行压缩,因此Java的primitive type的类型都有缩写形式:
boolean:没有类型,直接写入T/F
byte/short/integer:'I'
long:'L'
float/double:'D'
看实例代码:
// 序列化primitive type
ByteArrayOutputStream out = new ByteArrayOutputStream();
Hessian2Output hOut = new Hessian2Output(out);
hOut.writeInt(100);
hOut.flush();
byte[] bytes = out.toByteArray(); // 获取序列化的内容
hOut.close();
// 反序列化primitive type
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input hIn = new Hessian2Input(in);
int i = hIn.readInt();
hIn.close();
Hessian的类型优化1:Hessian实际上对经常使用的类型进行了优化,例如Byte/Short/Integer类型也编码为:'I',Long类型编码为:'L',Float/Double类型编码为:'D',java.util.Date类型编码为:'d',String/Character/char/StringBuilder编码为:'S';
Hessian的类型优化2:此优化仅仅对Hessian2有效,Hessian在序列化时不仅仅对写入的类型信息进行了优化,而且对类型的值(主要是正数和浮点数)也进行了优化;举个例子,我们写入一个200的long值,实际上我们只需要两个字节就可以存储而不是8个字节,同样对于float/double也可以节约非常多的空间,看下面的实例;
Hessian2Output hOut = new Hessian2Output(out);
hOut.writeInt(200);
hOut.flush();
byte[] bytes = out.toByteArray(); // 占用2个字节而不是4个字节!!
hOut.close();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input hIn = new Hessian2Input(in);
int i = hIn.readInt();
hIn.close();
- 序列化Reference Type
Hessian序列化一个对象,使用的是“com.caucho.hessian.io.JavaSerializer”和“com.caucho.hessian.io.JavaDeserializer”类型进行序列化和反序列化,其原理非常简单,首先序列化时通过反射获取类型的所有Field(属性),然后依次写入所有属性的值,反序列化则相反;需要说明的是,对于对象的属性,如果是primitive type或者是常用类型,也会使用上面说的优化机制;
备注:默认情况下Hessian也要求序列化的类型实现“java.io.Serializable”接口,不过可以通过配置对于为实现“”接口的类型也可以进行序列化。
简单实例:
TestVO vo = new TestVO();
vo.setI(1);
vo.setiObj(2);
vo.setJ(3L);
vo.setjObj(4L);
vo.setBigDec(BigDecimal.valueOf(5L));
vo.setBigInt(BigInteger.valueOf(6L));
vo.setStr("123");
// 序列化Reference对象
ByteArrayOutputStream out = new ByteArrayOutputStream();
Hessian2Output hOut = new Hessian2Output(out);
hOut.writeObject(vo);
hOut.flush();
byte[] bytes = out.toByteArray();
hOut.close();
// 反序列化Reference对象
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input hIn = new Hessian2Input(in);
TestVO deVo = (TestVO) hIn.readObject();
hIn.close(); - 序列化Array Type
序列化Array实例与一般的Reference实例没有太大的区别,只不过是编码规则不一样而已,对于一个Array实例而言,首先写入的是数组元素(component)的类型,接着就是数字的长度,然后依次写入每个元素的值; - 调用方法
上面的实例仅仅使用了Hessian提供的序列化功能,实际上Hessian提供了RPC功能,即在序列化功能的基础上,提供了打包调用远程方法必须要的额外信息,例如调用方法的接口、方法名称等等,同时将所有参数序列化为字节,一起打包为二进制流通过socket发送到远程服务器;而在服务器端,则通过读取接口类型、方法名称以及所有的参数信息,同时反序列化所有的参数实例,就可以调用指定对象的方法,最后将返回值传递给客户端,完成RPC的调用;
简单实例:
// 需要调用的接口,在项目中客户端只需要接口信息
public interface TestService {
String getName(String custNo, int flag);
}
// 接口的简单实现,在项目中实现都位于服务器端,客户端只需要接口
public class TestServiceImpl implements TestService {
@Override
public String getName(String custNo, int flag) {
return "Test Name";
}
}
// 编码调用信息
ByteArrayOutputStream out = new ByteArrayOutputStream();
Hessian2Output hOut = new Hessian2Output(out);
hOut.call("getName", new Object[]{"0000000009", 1}); // 因为在项目中一个接口对应一个servlet,所以不需要发送接口类型信息
hOut.flush();
byte[] bytes = out.toByteArray(); // 在项目中,生成的字节会通过socket发送到服务器端
hOut.close();
// 解码RPC信息并调用函数
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
Hessian2Input hIn = new Hessian2Input(in);
HessianInputFactory _inputFactory = new HessianInputFactory();
_inputFactory.readHeader(in);
hIn.readCall();
hIn.skipOptionalCall();
String methodName = hIn.readMethod(); // 读取调用的方法名称
int argLength = hIn.readMethodArgLength(); // 获取方法参数个数
String custName = (String)hIn.readObject(String.class); // 按顺序读取参数值
int flag = (Integer)hIn.readObject(int.class);
// 通过反射获取方法并调用
TestServiceImpl instance = new TestServiceImpl();
Method method = TestService.class.getDeclaredMethod(methodName, String.class, int.class);
Object result = method.invoke(instance, custName, flag); - 其他
在上面我们讲的RPC例子中,Hessian框架提供的功能要丰富并且更复杂,例如服务器端需要对Hessian的版本进行控制(有Hessian 1和Hessian 2两种),支持方法重载等等。