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)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hessian通信案例(java)
- 下一篇: hessiancpp编译和使用(C++版