前面的文章已經說了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兩種),支援方法重載等等。