Hessian 原理分析
一. 遠程通訊協議的基本原理
網絡通信需要做的就是將流從一臺計算機傳輸到另外一臺計算機,基於傳輸協議和網絡 IO 來實現,其中傳輸協議比較出名的有 http 、 tcp 、 udp 等等, http 、 tcp 、 udp 都是在基於 Socket 概念上爲某類應用場景而擴展出的傳輸協議,網絡 IO ,主要有 bio 、 nio 、 aio 三種方式,所有的分佈式應用通訊都基於這個原理而實現,只是爲了應用的易用,各種語言通常都會提供一些更爲貼近應用易用的應用層協議。
二. 應用級協議 Binary-RPC
Binary-RPC 是一種和 RMI 類似的遠程調用的協議,它和 RMI 的不同之處在於它以標準的二進制格式來定義請求的信息 ( 請求的對象、方法、參數等 ) ,這樣的好處是什麼呢,就是在跨語言通訊的時候也可以使用。
來看下 Binary -RPC 協議的一次遠程通信過程:
1 、客戶端發起請求,按照 Binary -RPC 協議將請求信息進行填充;
2 、填充完畢後將二進制格式文件轉化爲流,通過傳輸協議進行傳輸;
3 、接收到在接收到流後轉換爲二進制格式文件,按照 Binary -RPC 協議獲取請求的信息並進行處理;
4 、處理完畢後將結果按照 Binary -RPC 協議寫入二進制格式文件中並返回。
問題總結:
1 、傳輸的標準格式是?
標準格式的二進制文件。
2 、怎麼樣將請求轉化爲傳輸的流?
將二進制格式文件轉化爲流。
3 、怎麼接收和處理流?
通過監聽的端口獲取到請求的流,轉化爲二進制文件,根據協議獲取請求的信息,進行處理並將結果寫入 XML 中返回。
4 、傳輸協議是?
Http 。
三. Hessian ——一種實現遠程通訊的 library
Hessian 是由 caucho 提供的一個基於 binary-RPC 實現的遠程通訊 library 。
1 、是基於什麼協議實現的?
基於 Binary-RPC 協議實現。
2 、怎麼發起請求?
需通過 Hessian 本身提供的 API 來發起請求。
3 、怎麼將請求轉化爲符合協議的格式的?
Hessian 通過其自定義的串行化機制將請求信息進行序列化,產生二進制流。
4 、使用什麼傳輸協議傳輸?
Hessian 基於 Http 協議進行傳輸。
5 、響應端基於什麼機制來接收請求?
響應端根據 Hessian 提供的 API 來接收請求。
6 、怎麼將流還原爲傳輸格式的?
Hessian 根據其私有的串行化機制來將請求信息進行反序列化,傳遞給使用者時已是相應的請求信息對象了。
7 、處理完畢後怎麼迴應?
處理完畢後直接返回, hessian 將結果對象進行序列化,傳輸至調用端。
四. Hessian 源碼分析
以 hessian 和 spring dm server 整合環境爲例。
- 1. 客戶端發起請求
Hessian 的這個遠程過程調用,完全使用動態代理來實現的。有客戶端可以看出。
除去 spring 對其的封裝,客戶端主要是通過 HessianProxyFactory 的 create 方法就是創建接口的代理類,該類實現了接口, JDK 的 proxy 類會自動用 InvocationHandler 的實現類(該類在 Hessian 中表現爲 HessianProxy )的 invoke 方法體來填充所生成代理類的方法體。
客戶端系統啓動時:
根據 serviceUrl 和 serviceInterface 創建代理。
HessianProxyFactoryBean 類
HessianClientInterceptor 類
createHessianProxy(HessianProxyFactory proxyFactory)
HessianProxyFactory 類
public Object create(Class api, String urlName)
客戶端調用 hessian 服務時:
HessianProxy 類的 invoke(Object proxy, Method method, Object []args) 方法
String methodName = method.getName();// 取得方法名
Object value = args[0]; // 取得傳入參數
conn = sendRequest(mangleName, args) ; // 通過該方法和服務器端取得連接
httpConn = (HttpURLConnection) conn;
code = httpConn.getResponseCode(); // 發出請求
// 等待服務器端返回相應…………
is = conn.getInputStream();
Object value = in.readObject(method.getReturnType()); // 取得返回值
HessianProxy 類的 URLConnection sendRequest(String methodName, Object []args) 方法:
URLConnection conn = _factory.openConnection(_url); // 創建 URLConnection
OutputStream os = conn.getOutputStream();
AbstractHessianOutput out = _factory.getHessianOutput(os); // 封裝爲 hessian 自己的輸入輸出 API
out.call(methodName, args);
return conn;
- 2. 服務器端接收請求並處理請求
服務器端截獲相應請求交給:
org.springframework.remoting.caucho.HessianServiceExporter
具體處理步驟如下:
a) HessianServiceExporter 類
(HessianExporter) invoke(request.getInputStream(), response.getOutputStream());
b) HessianExporter 類
(Hessian2SkeletonInvoker) this.skeletonInvoker.invoke(inputStream, outputStream);
c) Hessian2SkeletonInvoker 類
將輸入輸出封轉化爲轉化爲 Hessian 特有的 Hessian2Input 和 Hessian2Output
Hessian2Input in = new Hessian2Input(isToUse);
in.setSerializerFactory(this.serializerFactory);
AbstractHessianOutput out = null;
int major = in.read();
int minor = in.read();
out = new Hessian2Output(osToUse);
out = new HessianOutput(osToUse);
out.setSerializerFactory(this.serializerFactory);
(HessianSkeleton) this.skeleton.invoke(in, out);
d) HessianSkeleton 類
讀取方法名
String methodName = in.readMethod();
Method method = getMethod(methodName);
讀取方法參數
Class []args = method.getParameterTypes();
Object []values = new Object[args.length];
執行相應方法並取得結果
result = method.invoke(service, values);
結果寫入到輸出流
out.writeObject(result);
總結: 由上面源碼分析可知,客戶端發起請求和服務器端接收處理請求都是通過 hessian 自己的 API 。輸入輸出流都要封裝爲 hessian 自己的 Hessian2Input 和 Hessian2Output ,接下來一節我們將去了解 hessian 自己封裝的輸入輸出到底做了些什麼!
五. Hessian 的序列化和反序列化實現
hessian 源碼中 com.caucho.hessian.io 這個包是 hessian 實現序列化與反序列化的核心包。其中 AbstractSerializerFactory , AbstractHessianOutput , AbstractSerializer , AbstractHessianInput , AbstractDeserializer 是 hessian 實現序列化和反序列化的核心結構代碼。
- AbstractSerializerFactory ,它有 2 個抽象方法:
根據類來決定用哪種序列化工具類
abstract public Serializer getSerializer(Class cl) throws HessianProtocolException;
根據類來決定用哪種反序列化工具類
abstract public Deserializer getDeserializer(Class cl) throws HessianProtocolException;
- SerializerFactory 繼承 AbstractSerializerFactory 。
在 SerializerFactory 有很多靜態 map 用來存放類與序列化和反序列化工具類的映射,這樣如果已經用過的序列化工具就可以直接拿出來用,不必再重新實例化工具類。
在 SerializerFactory 中,實現了抽象類的 getSerializer 方法,根據不同的需要被序列化的類來獲得不同的序列化工具,一共有 17 種序列化工具, hessian 爲不同的類型的 java 對象實現了不同的序列化工具,默認的序列化工具是 JavaSerializer 。
在 SerializerFactory 中,也實現了抽象類的 getDeserializer 方法,根據不同的需要被反序列化的類來獲得不同的反序列化工具,默認的反序列化工具類是 JavaDeserializer 。
- HessianOutput 繼承 AbstractHessianOutput 成爲序列化輸出流的一種實現。
它會實現很多方法,用來做流輸出。
需要注意的是方法,它會先調用 serializerFactory 根據類來獲得 serializer 序列化工具類
public void writeObject(Object object)
throws IOException
{
if (object == null) {
writeNull();
return;
}
Serializer serializer;
serializer = _serializerFactory.getSerializer(object.getClass());
serializer.writeObject(object, this);
}
- 現在我們來看看 AbstractSerializer 。
其 writeObject 是必須在子類實現的方法, AbstractSerializer 有 17 種子類實現, hessian 根據不同的 java 對象類型來實現了不同的序列化工具類,其中默認的是 JavaSerializer 。
而 JavaSerializer 的 writeObject 方法的實現,遍歷 java 對象的數據成員,根據數據成員的類型來獲得各自的 FieldSerializer ,一共有 6 中默認的 FieldSerializer 。
拿默認的 FieldSerializer 舉例,還是調用 AbstractHessianOutput 的子類來 writeObject ,這個時候,肯定能找到相應的 Serializer 來做序列化
同理可以反推出 hessian 的反序列化機制。 SerializerFactory 可以根據需要被反序列化的類來獲得反序列化工具類來做反序列化操作。
總結:得益於 hessian 序列號和反序列化的實現機制, hessian 序列化的速度很快,而且序列化後的字節數也較其他技術少。