Hessian源码分析(java)

个人博客: 戳我,戳我

先扯一扯

前一篇博文Hessian通信案例(java)简单实现了Java版的Hessian客户端和服务端的通信,总体看来,实现起来比较简单,整个基于Hessian的远程调用过程也显得很方便。但是知其然还要知其所以然,Hessian的底层是怎么实现远程调用的?是怎么序列化的?又是怎么反序列化的?又是如何通信的?

还记得吗

下面这段代码你还记得吗?

String url = "http://localhost:8080/hessian_server/ServerMachineTest";   
HessianProxyFactory factory = new HessianProxyFactory();   
IBasic basic = (IBasic) factory.create(IBasic.class, url); 
String helloreturn = basic.hello();

上面这段代码就是前一篇博文中实现的客户端的代码,实现远程过程调用就短短的四行代码,如此简单。但是new HessianProxyFactory()是做什么的?factory.create()又是怎么实现的?

一层层的剥去外衣

项目姿势微调

为了探究new HessianProyFactory()具体实现了什么,需要对之前博文中实现的案例进行一点调整。案例中是直接导入了hessian-4.0.7.jar作为lib库的方式,为了在Eclipse中进行单步调试,需要用源码(hessian-4.0.7-src.jar)来替代这个jar包。这里需要注意的是版本,可能会出现兼容性的问题,具体情况可以试错。

导入源码包替换了jar包之后的效果:

client包主要是client端使用的功能,里面就是动态代理和http连接等操作,io包则是处理序列化。
这里需要注意的是,可能会出现一些错误,可能需要添加commons-io-2.4.jar这个包以及Tomcat的runtime环境。具体的话自行解决。

调整成这样后,启动Tomcat,启动Hessian服务端就可以对客户端进行单步调试了!O(∩_∩)O哈哈~

启动Hessian服务端


启动Hessian服务端后先进行下测试,运行刚刚调整过的Hessian客户端,看看有没有出错?正常情况会出现下面的结果:

接下来就可以对客户端进行单步调试了。

单步调试

在Eclipse中进行代码调试很方便,要在哪一行设置断点,只需要在行首进行双击,就可以看到一个圆点。F11(Debug),F5(step into),F6(step over)。

HessianProxyFactory factory = new HessianProxyFactory();   
IBasic basic = (IBasic) factory.create(IBasic.class, url); 
String helloreturn = basic.hello();

前两句没什么特殊的,new了一个动态代理工厂,这个工厂负责调用底层的序列化方法进行序列化;creat()函数根据定义好的接口函数以及设置好的服务端的地址进行一些处理。真正实现远程调用的是第三句代码,如下图,我在String helloreturn = basic.hello()这句代码设置断点。,启动调试,可以看到进入到了一个函数invoke()。

public Object invoke(Object proxy, Method method, Object []args)
  throws Throwable
    {
  String mangleName;

  synchronized (_mangleMap) {
    mangleName = _mangleMap.get(method);
  }

  if (mangleName == null) {
    String methodName = method.getName();
    Class<?> []params = method.getParameterTypes();

    // equals and hashCode are special cased
    if (methodName.equals("equals")
  && params.length == 1 && params[0].equals(Object.class)) {
Object value = args[0];
if (value == null || ! Proxy.isProxyClass(value.getClass()))
  return Boolean.FALSE;

Object proxyHandler = Proxy.getInvocationHandler(value);

if (! (proxyHandler instanceof HessianProxy))
  return Boolean.FALSE;

HessianProxy handler = (HessianProxy) proxyHandler;

return new Boolean(_url.equals(handler.getURL()));
    }
    else if (methodName.equals("hashCode") && params.length == 0)
return new Integer(_url.hashCode());
    else if (methodName.equals("getHessianType"))
return proxy.getClass().getInterfaces()[0].getName();
    else if (methodName.equals("getHessianURL"))
return _url.toString();
    else if (methodName.equals("toString") && params.length == 0)
return "HessianProxy[" + _url + "]";

    if (! _factory.isOverloadEnabled())
mangleName = method.getName();
    else
      mangleName = mangleName(method);

    synchronized (_mangleMap) {
_mangleMap.put(method, mangleName);
    }
  }

  InputStream is = null;
  HessianConnection conn = null;

  try {
    if (log.isLoggable(Level.FINER))
log.finer("Hessian[" + _url + "] calling " + mangleName);

    conn = sendRequest(mangleName, args);

    is = conn.getInputStream();

    if (log.isLoggable(Level.FINEST)) {
PrintWriter dbg = new PrintWriter(new LogWriter(log));
HessianDebugInputStream dIs
  = new HessianDebugInputStream(is, dbg);

dIs.startTop2();

is = dIs;
    }

    AbstractHessianInput in;

    int code = is.read();

    if (code == 'H') {
int major = is.read();
int minor = is.read();

in = _factory.getHessian2Input(is);

Object value = in.readReply(method.getReturnType());

return value;
    }
    else if (code == 'r') {
int major = is.read();
int minor = is.read();

in = _factory.getHessianInput(is);

in.startReplyBody();

Object value = in.readObject(method.getReturnType());

if (value instanceof InputStream) {
  value = new ResultInputStream(conn, is, in, (InputStream) value);
  is = null;
  conn = null;
}
else
  in.completeReply();

return value;
    }
    else
throw new HessianProtocolException("'" + (char) code + "' is an unknown code");
  } catch (HessianProtocolException e) {
    throw new HessianRuntimeException(e);
  } finally {
    try {
if (is != null)
  is.close();
    } catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
    }

    try {
if (conn != null)
  conn.destroy();
    } catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
    }
  }
}

客户端任何远程调用函数都会经由invoke函数实现,其中关键的几句代码如下:

log.finer("Hessian[" + _url + "] calling " + mangleName);

conn = sendRequest(mangleName, args);

mangleName可能就是定义好的接口,比如我的hello函数名,args就是接口函数的参数,最后通过sendRequest函数和服务端通信。下面着重看下这个函数的实现:

protected HessianConnection sendRequest(String methodName, Object []args)
  throws IOException
 {
  HessianConnection conn = null;

  conn = _factory.getConnectionFactory().open(_url);
  boolean isValid = false;

  try {
    addRequestHeaders(conn);

    OutputStream os = null;

    try {
os = conn.getOutputStream();
    } catch (Exception e) {
throw new HessianRuntimeException(e);
    }

    if (log.isLoggable(Level.FINEST)) {
PrintWriter dbg = new PrintWriter(new LogWriter(log));
HessianDebugOutputStream dOs = new HessianDebugOutputStream(os, dbg);
dOs.startTop2();
os = dOs;
    }

    AbstractHessianOutput out = _factory.getHessianOutput(os);

    out.call(methodName, args);
    out.flush();

    conn.sendRequest();

    isValid = true;

    return conn;
  } finally {
    if (! isValid && conn != null)
conn.destroy();
  }
 }

可以看到这个函数的实现方法是,构造http的协议头,通过call()函数后再通过sendRequest()函数发送出去。调试到conn.sendRequest()函数的时候阻塞了(如果你没有启动服务端的话),可见最后走http协议发送的任务就是由conn.sendRequest()完成的。那么out.call(methodName,args)又做了什么呢?可以告诉你,这个函数真正实现了报文内容的序列化

public void call(String method, Object []args)
  throws IOException
{
  int length = args != null ? args.length : 0;

  startCall(method, length);

  for (int i = 0; i < length; i++)
    writeObject(args[i]);

  completeCall();
 }

代码很明了,可以猜测startCall(method,length)实现对方法名(也即接口函数名)的序列化;然后对接口函数的每一个参数调用writeObject()进行序列化,这是重头戏。最后completeCall()进行了序列化的收尾工作。

具体序列化的过程我就不跟进去了。call()函数完成了hessian的序列化,下面是对hello()这个函数序列化后的hessian报文:

可以看到序列化后包含了一些不可见的字符,下面这个是用十六进制查看的。由于我是回过头来写这篇博文的,所以对于hessian的序列化机制是知道的。上面序列的方式是字符’c’后面追加hessian的版本,然后字符’m’代表method,然后是接口函数名hello,然后是函数的参数(此处由于hello函数参数为空,故没有),最后追加序列化结束的标志,字符’z’。

这是比较简单的函数,简单的参数,如果碰到比较复杂的函数和参数,序列化的过程会更复杂。具体请看hessian协议2.0序列化规则。

——————————————–我是分割线—————————————————————-
——————————————–后来追加的—————————————————————-
初写这篇博文的时候没打算跟进writeObject(args[i])函数,后来打算加进去这部分的分析过程,比较重头戏就是序列化和反序列化。由于上面的案例用到的接口函数string hello()比较简单,没有参数,故此处重新换一个函数String hello_2(int arg1,String arg2)进行调试分析。同样,调试过程进入到call()函数,由于参数有两个,故writeObject()函数将执行两次。代码如下:

public void writeObject(Object object)
throws IOException
{
if (object == null) {
  writeNull();
  return;
}

Serializer serializer;

serializer = _serializerFactory.getSerializer(object.getClass());

serializer.writeObject(object, this);
}

其中关键代码:

serializer = _serializerFactory.getSerializer(object.getClass());

这句代码就是根据参数对象的类型寻找相匹配的序列化器,进行序列化。正如getSerializer的代码注释一样:

/**
 * Returns the serializer for a class.
 *
 * @param cl the class of the object that needs to be serialized.
 *
* @return a serializer object for the serialization.
*/

找到序列化器之后真正根据hessian协议执行序列化的是serializer.writeObject(object,this)函数:

public void writeObject(Object obj, AbstractHessianOutput out)
throws IOException
{
switch (_code) {
case BOOLEAN:
  out.writeBoolean(((Boolean) obj).booleanValue());
  break;

case BYTE:
case SHORT:
case INTEGER:
  out.writeInt(((Number) obj).intValue());
  break;

case LONG:
  out.writeLong(((Number) obj).longValue());
  break;

case FLOAT:
case DOUBLE:
  out.writeDouble(((Number) obj).doubleValue());
  break;

case CHARACTER:
case CHARACTER_OBJECT:
  out.writeString(String.valueOf(obj));
  break;

case STRING:
  out.writeString((String) obj);
  break;

case DATE:
  out.writeUTCDate(((Date) obj).getTime());
  break;

case BOOLEAN_ARRAY:
{
  if (out.addRef(obj))
    return;

  boolean []data = (boolean []) obj;
  boolean hasEnd = out.writeListBegin(data.length, "[boolean");
  for (int i = 0; i < data.length; i++)
    out.writeBoolean(data[i]);

  if (hasEnd)
out.writeListEnd();

  break;
}

case BYTE_ARRAY:
{
  byte []data = (byte []) obj;
  out.writeBytes(data, 0, data.length);
  break;
}

case SHORT_ARRAY:
{
  if (out.addRef(obj))
    return;

  short []data = (short []) obj;
  boolean hasEnd = out.writeListBegin(data.length, "[short");

  for (int i = 0; i < data.length; i++)
    out.writeInt(data[i]);

  if (hasEnd)
out.writeListEnd();
  break;
}

case INTEGER_ARRAY:
{
  if (out.addRef(obj))
    return;

  int []data = (int []) obj;

  boolean hasEnd = out.writeListBegin(data.length, "[int");

  for (int i = 0; i < data.length; i++)
    out.writeInt(data[i]);

  if (hasEnd)
out.writeListEnd();

  break;
}

case LONG_ARRAY:
{
  if (out.addRef(obj))
    return;

  long []data = (long []) obj;

  boolean hasEnd = out.writeListBegin(data.length, "[long");

  for (int i = 0; i < data.length; i++)
    out.writeLong(data[i]);

  if (hasEnd)
out.writeListEnd();
  break;
}

case FLOAT_ARRAY:
{
  if (out.addRef(obj))
    return;

  float []data = (float []) obj;

  boolean hasEnd = out.writeListBegin(data.length, "[float");

  for (int i = 0; i < data.length; i++)
    out.writeDouble(data[i]);

  if (hasEnd)
out.writeListEnd();
  break;
}

case DOUBLE_ARRAY:
{
  if (out.addRef(obj))
    return;

  double []data = (double []) obj;
  boolean hasEnd = out.writeListBegin(data.length, "[double");

  for (int i = 0; i < data.length; i++)
    out.writeDouble(data[i]);

  if (hasEnd)
out.writeListEnd();
  break;
}

case STRING_ARRAY:
{
  if (out.addRef(obj))
    return;

  String []data = (String []) obj;

  boolean hasEnd = out.writeListBegin(data.length, "[string");

  for (int i = 0; i < data.length; i++) {
    out.writeString(data[i]);
  }

  if (hasEnd)
out.writeListEnd();
  break;
}

case CHARACTER_ARRAY:
{
  char []data = (char []) obj;
  out.writeString(data, 0, data.length);
  break;
}

case OBJECT_ARRAY:
{
  if (out.addRef(obj))
    return;

  Object []data = (Object []) obj;

  boolean hasEnd = out.writeListBegin(data.length, "[object");

  for (int i = 0; i < data.length; i++) {
    out.writeObject(data[i]);
  }

  if (hasEnd)
out.writeListEnd();
  break;
}

case NULL:
  out.writeNull();
  break;

case OBJECT:
  ObjectHandleSerializer.SER.writeObject(obj, out);
  break;

case BYTE_HANDLE:
  out.writeObject(new ByteHandle((Byte) obj));
  break;

case SHORT_HANDLE:
  out.writeObject(new ShortHandle((Short) obj));
  break;

case FLOAT_HANDLE:
  out.writeObject(new FloatHandle((Float) obj));
  break;

default:
  throw new RuntimeException(_code + " unknown code for " + obj.getClass());
}
}
}

可以看到根据不同的参数类型,调用相关的基础序列化函数执行。

到此,就很好理解了。hessian的序列化支持基本的类型,int,double,long,date,string等。序列化的方式是把接口函数名和参数根据一定的规则进行序列化,然后走http信道发送到服务端。

——————————————我是分割线END———————————————————–

回到sendRequest函数发送完hessian报文后,回到invoke函数,接下来就是对服务端返回的内容进行反序列化:

if (code == 'H') {
int major = is.read();
int minor = is.read();

in = _factory.getHessian2Input(is);

Object value = in.readReply(method.getReturnType());

return value;
    }
    else if (code == 'r') {
int major = is.read();
int minor = is.read();

in = _factory.getHessianInput(is);

in.startReplyBody();

Object value = in.readObject(method.getReturnType());

两个if判断只是为了确定服务端的序列化版本,’H’代表服务端是用2.0,’r’代表服务端是采用1.0的序列化方法。真正进行反序列的函数分别是readReply()和readObject()函数。

具体实现细节此处就不赘述了。反序列化对应序列化,是一个相反的过程。最终反序列化得到服务端返回的hessian报文。

调试完了

至此,客户端的底层实现细节就披露完了,简单讲,调用接口函数后进入invoke函数,invoke函数构造http头,调用call函数进行序列化,调用sendRequest函数进行发送,然后调用readReply或者readObject函数进行反序列化,得到服务端返回的应答。

服务端呢?

服务端的序列化和反序列化方式和客户端大同小异,差别只是一些头部和尾部的构造等。此处就略去不分析了。

关于服务端的调试也是一样的方式,设置断点,然后Debug。

复杂的类型呢?

上面讲的都是比较简单的函数,序列化过程比较简单,如果碰到比较复杂的函数呢?例如下面的函数:

CTrade hello(string arg1,int  arg2,list<int>  arg3,map<string,string> arg4...);

这里就自己去探索了,研究的方式也是一样,单步调试加日志记录。当时我的项目里做的是hessian与xml之间的转换,hessain报文比较复杂,层次结构比较多,涉及到的类型也很多,后来对这些做了一些研究,参照Java版的hessian采用c++实现了GXP(公司里的一个c++平台)上的一些组包解包。当然这也涉及到c++版的hessian的使用等。情况比较复杂,如果有时间,后面的博文会简要记录下关于这部分的内容。

完事了

可能由于我现在是回过头来记录这些内容,关于hessian的序列化和反序列,我的感觉是比较简单。但当时由于刚刚接触到hessian,而且能找到的资料里基本都是java版的,对于一个从事c++开发的人来说,当然也不是什么难事,所以花时间研究了java版的hessian的案例实现,源码实现等。当然,其实还是有点复杂的。我现在属于站着说话不腰疼,好了伤疤忘了疼。哈哈!

Blog:

2016-11-18 于杭州
By 史矛革

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值