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 casedif (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();elsemangleName = 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;
}
elsein.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:
rebootcat.com (默认)
email: linuxcode2niki@gmail.com
2016-11-18 于杭州
By 史矛革
Hessian源码分析(java)相关推荐
- 深入源码分析Java线程池的实现原理
转载自 深入源码分析Java线程池的实现原理 程序的运行,其本质上,是对系统资源(CPU.内存.磁盘.网络等等)的使用.如何高效的使用这些资源是我们编程优化演进的一个方向.今天说的线程池就是一种对 ...
- 视频教程-Spring底层源码分析-Java
Spring底层源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大促活动技术保 ...
- 视频教程-RPC服务框架(Dubbo)源码分析-Java
RPC服务框架(Dubbo)源码分析 鲁班学院-子路老师曾就职于谷歌.天猫电商等多家互联网公司,历任java架构师.研发经理等职位,参与并主导千万级并发电商网站与后端供应链研发体系搭建,多次参与电商大 ...
- JDK源码分析——Java的SPI机制分析与实战
重点提示:在我博客中的所有的源码分析的实例,我都将会放到github上,感兴趣的朋友可以下载下来调试运行,我相信还是可以有所收获的.我的目的是让所有读到我博客的朋友都可以了解到有价值的东西,学习到ja ...
- java类加载机制为什么双亲委派_[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的...
Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的不过源码其实比较简单,接下来简单介绍一下我们先从启动类说起有一个Launcher类 ...
- 从源码分析java.lang.String.isEmpty()
今天在写代码的时候用到了java.lang.String.isEmpty()的这个方法,之前也用过,今天突发奇想,就看了看源码,了解了解它的实现方法,总结出来,大家可以交流交流. 通常情况下,我们使用 ...
- java string is empty_从源码分析java.lang.String.isEmpty()
今天在写代码的时候用到了java.lang.String.isEmpty()的这个方法,之前也用过,今天突发奇想,就看了看源码,了解了解它的实现方法,总结出来,大家可以交流交流. 通常情况下,我们使用 ...
- java jdbc(mysql)驱动源码分析,JAVA JDBC(MySQL)驱动源码分析(四)
connect方法是java.sql.Driver接口中定义的方法,如果连接的数据库不同,那么为不同的数据库编写JDBC驱动将变得很灵活,实现Driver接口即可.连接数据库时首先得装载JDBC驱动, ...
- 【JAVA源码分析——Java.lang】String源码分析
String 基本实现 初学java经常会误认为String是java基本类型,实际上String并非Java基本类型,String本质上是对char数组的封装. 以下是String实现相关源码 /* ...
最新文章
- 分享WCF聊天程序--WCFChat
- es6关于let和const的总结
- 高频面试题2:单例设计模式
- jquery 里面对数组操作-去重
- 使用Struts接受Ajax请求并且返回Json数据
- Git CMD - diff: Show changes between commits, commit and working tree, etc
- 瀑布流布局的实现方式
- [Python] L1-031. 到底是不是太胖了-PAT团体程序设计天梯赛GPLT
- Typecho中的gravatar头像无法加载
- linux内核溢出利用,Linux内核堆缓冲区溢出漏洞
- 「经典题」完整的前端项目开发流程
- 美国的知名的人工智能研究机构或者实验室
- Geometric tampering estimation by means of a sift-based forensic analysis论文阅读
- 图的生成树与生成森林
- 微信小程序实现预览图片
- GParted图形化工具对Linux磁盘分区扩容
- 定时任务Schedule的使用
- 计算机硬件甩,计算机硬件 篇一:手把手教你更新CPU微码-x99平台最后的挣扎
- 尼尔机器人技能快捷键_尼尔机械纪元武器配置及全出招操作详解-游侠网
- Asp.Net避免按钮重复点击