Hessian原理與程序設計

  Hessian是比較常用的binary-rpc。性能較高,適合互聯網應用。主要使用在普通的webservice 方法調用。交互數據較小的場景中。hessian的數據交互基於http協議,一般hessian的server端設計需要使用到web server容器(比方servlet等)。你可以將不論什麼Java類暴露給HessianServlet,並公佈成hessian服務;那麼hessian client將可以經過相似調用servlet同樣,得到遠程方法的輸出結果。java

    因爲hessian的接口調用基於http,且以字節碼的方式進行數據交換,那麼hessian需要提供本身定義的「protocol」以及序列化/反序列機制。web

一般咱們以爲hessian的這樣的方式是高效而且簡潔的,只是hessian中使用的反射機制/序列化機制/動態代理都是基於java原生API。(Java自帶的序列化是否高效,在此就不在爭論了)redis

 

一.Hessian原理與協議簡析:spring

    http的協議約定了傳輸數據的方式,hessian也沒法改變太多:數據庫

    1) hessian中client與server的交互,基於http-post方式。spring-mvc

    2) hessian將輔助信息。封裝在http header中,比方「受權token」等,咱們可以基於http-header來封裝關於「安全校驗」「meta數據」等。hessian提供了簡單的"校驗"機制。安全

    3) 對於hessian的交互核心數據,比方「調用的方法」和參數列表信息。將經過post請求的body體直接發送。格式爲字節流。cookie

    4) 對於hessian的server端響應數據,將在response中經過字節流的方式直接輸出。網絡

 

    hessian的協議自己並不複雜。在此再也不贅言。所謂協議(protocol)就是約束數據的格式,client依照協議將請求信息序列化成字節序列發送給server端,server端依據協議,將數據反序列化成「對象」,而後運行指定的方法。並將方法的返回值再次依照協議序列化成字節流,響應給client,client依照協議將字節流反序列話成"對象"。mvc

 

Client端:

    在Client端,核心API爲:

    1) HessianProxyFactory: 負責託管"遠程接口"和"遠程hessian服務的URL",並生成代理類(Java Proxy實例)。

    2) HessianProxy: Proxy實例的驅動器(handler),當代理實例的方法調用時,HessianProxy負責序列化"方法名"/"參數列表"等,並調用遠程URL獲取響應數據;同一時候也負責反序列化。

底層使用HttpURLConnection。

    3) HessianOutput: 負責將序列化的字節數據,依照協議,寫入inputStream,並經過URL Connection發送給遠端。

 

    hessian-client發送請求的首要條件,就是需要指明url,此url就是server端暴露的servlet地址。

 

 

Java代碼   收藏代碼
  1. HessianProxyFactory proxyFactory = new HessianProxyFactory();  
  2. HelloService service = (HelloService)proxyFactory.create(HelloService.class"http://localhost:8080/hessian/helloService");  
  3. System.out.println(service.sayHello("hessian"));  
 

 

    上述代碼例子中,HelloService表示遠程接口API,當中URL中「helloService」表示調用的「服務名稱」,這個「服務名稱」有server端決定。因此Client需要首先知道所需服務的URL全路徑。

那麼方法的調用數據,將會依照例如如下「序列」發送(俗稱「字節碼成幀」):

   

Java代碼   收藏代碼
  1. [「方法名稱「的字節長度]["方法名稱」字節序列][參數的個數]{[參數類型][」參數「的字節長度][」參數「的字節序列]...}  
    比方調用sayHello(String message)方法,那麼序列化的字節流格式可能爲:

 

Java代碼   收藏代碼
  1. [8][sayHello][1]['S'][5]['hello']  
  2. //當中「8」表示「sayHello」方法名稱爲8個字節  
  3. //"1"表示參數的個數爲1  
  4. //「S」表示參數的類型爲「String」,hessian中定義了大量的簡寫字母。用來表示java數據類型  
  5. //「5」表示參數的字節個數爲5  
  6. //"hello"表示此參數的值爲「hello」,只是實際上傳輸的應該是「hello」相應的字節序列。

      

 

    假設你從事過socket通訊方面的開發。你應該知道server端會怎樣「解析」這個字節流信息。對於Hessian-server端而言,也是依據「字節碼成幀」,逐個讀取信息,比方首先讀取一個32位的int。獲得「8」,而後讀取8個字節並使用utf8的方式編碼成String字符串,將得到「sayHello」,此時server端已經可以知道client需要調用的方法名稱爲「sayHello」。而後對於一個32爲的int。獲得1,表示參數列表的個數爲1;而後在讀取一個字節。得到「S」,表示參數爲String類型.....

 

    Server端運行結束後,Http響應的字節流格式基本和上述相似。

那麼HessianProxy負責反序列化和類型轉換就能夠。

 

    需要注意。Hessian Client默認不支持「重載」方法的調用,一般咱們需要開啓「OverloadEnabled」屬性設置爲true。此後在方法調用時。Hessian將會把「方法簽名」 + 「_」 + 「參數類型」做爲新的方法名稱發送給Server端。 

 

Java代碼   收藏代碼
  1. proxyFactory.setOverloadEnabled(true);//默以爲false  

 

 

Server端:

    在Server端最重要的類,就是HessianServlet;它是一個普通的Java Servlet實現。每個「服務」都需要配置一個HessianServlet實例,它負責接收Client發送的Http請求,請求類型必須是POST。

 

 

Java代碼   收藏代碼
  1. <servlet>  
  2.     <servlet-name>helloService</servlet-name>  
  3.     <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>  
  4.     <init-param>  
  5.         <param-name>service-class</param-name>  
  6.         <param-value>com.test.hessian.impl.HelloServiceImpl</param-value>  
  7.     </init-param>  
  8. </servlet>  
  9.   
  10. <servlet-mapping>  
  11.     <servlet-name>helloService</servlet-name>  
  12.     <url-pattern>/hessian/helloService</url-pattern>  
  13. </servlet-mapping>  
 

 

    從上述實例中。咱們看出HessianServlet需要一個重要的初始化參數「service-class」,它的值必須是一個良好的「遠程接口」實現類(默認構造器)。

HessianSkeleton和Client端的HessianProxy相應,它是負責server端Http請求的核心類,每個HessianServlet都會持有一個HessianSkeleton實例,這個實例持有「service-class」對象。並在初始化時經過反射機制將「service-class」的所有方法列表放在一個methodMap中,key爲「方法名」,value爲Method(java.lang.reflect.Method),同一時候爲了不方法重載。還會額外的將「方法名_參數類型」做爲一個新key。也放入methodMap中。

 

    在HessianServlet初始化時,會經過反射機制的方式建立一個「service-class」的實例,當Http請求到達Servlet時,HessianSkeleton實例負責從請求中解析出「方法名」和參數列表;那麼到此爲止。一切就很是清楚了,HessianSkeleton依據方法名,從methodMap中獲取Method,並調用其invode方法。而後將運行結果反序列化。寫入Response。

 

    1) Hessian的client每次調用,都會開啓一個新的http連接,因此各個調用之間沒法共享數據,其實你可以使用cookie技術來保存相關數據。但是其實收效甚微。

    2) Hessian的client使用了java的動態代理,因爲client需要明白知道接口的API信息。

    3) Hessian的接口API中,不論是方法的參數仍是方法的返回值,都必須是可序列化的(實現Serializable接口),你可以經過重寫Serializable接口的相關方法。來本身定義序列化/反序列化操做。

    4) 不要嘗試使用hessian交互大數據。

    5) 不要嘗試對hessian的交互數據的二進制流進行額外的加密,這是一件得不償失的事情,儘管看起來安全,其實收效甚微。

 
二.http請求與「冪等性」:

    所謂「冪等性」。可以簡單理解爲:就是對一個資源使用一樣的參數進行訪問,每次得到的結果應該是一樣的。或者說「資源內容的變動」是符合預期的(比方數字自增操做);當中http get請求以爲是「冪等」的。但是http中經過post請求的方式致使server端數據變動,在某些狀況下,可能打破「冪等性」。比方對server端的商品數量進行「減法」操做,假設請求發送成功,server運行成功,但是網絡異常致使client未能受到結果。假設此時client重試,將會致使server端再次運行,但是其實。數量被反覆計算了一次。

    那麼對與http-webservice而言,咱們需要注意這一點,對與數據變動的接口調用,要麼在接口中進行合理的邏輯校驗。要麼使用相似於「二階段」提交的方式來作控制:

    1) 首先發送一次請求,獲取version,此version多是數據庫的樂觀鎖的version。也多是redis/zookeeper等數據中心的一個惟一值。比方,咱們在訂單表中,每個orderId都相應一個version。每次數據操做都會致使version++;那麼在hessian接口中需要改動訂單信息時,首先獲取此version。

    2) client傳遞需要變動的信息,同一時候也交付1)中獲取的version。那麼server端運行數據變動時,首先檢測version是否一致,假設一致則改動,不然響應給client一個「重試」信號,那麼client需要又一次獲取version。而後繼續提交。

 

 

三.核心API:

 

    1) HessianProxy

    2) HessianProxyFactory

    3) HessianInput:輸入流控制。用來反序列化響應的結果,當中包含remote端的異常棧(在client端將會被又一次拋出),「Fault」信息(remote端的失敗信息,比方格式錯誤等)。很是多時候。你可以經過指定http response-code值來實現特定的請求失敗信號。

    4) HessianOutput:輸出流控制。用來序列化請求的數據

    5) Deserializer:反序列化接口,大量的實現類用於反序列化不一樣類型的數據

    6) Serializer:序列化接口

 

四.程序實例(基於spring MVC):

 

    1. web.xml(Server端)

Java代碼   收藏代碼
  1. <servlet>  
  2.     <servlet-name>hessianService</servlet-name>  
  3.     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.     <init-param>  
  5.         <param-name>contextConfigLocation</param-name>  
  6.         <param-value>classpath:spring-mvc-servlet.xml</param-value>  
  7.     </init-param>  
  8.     <load-on-startup>1</load-on-startup>  
  9. </servlet>  
  10.   
  11. <servlet-mapping>  
  12.     <servlet-name>hessianService</servlet-name>  
  13.     <url-pattern>/hessian/*</url-pattern>  
  14. </servlet-mapping>  

    這裏有個奇怪的問題。假設hessian是經過spring公佈的,那麼url-pattern需要以「/hessian」前綴開頭。 

 

     2. spring-mvc-servlet.xml(Server端)

Java代碼   收藏代碼
  1. <?

    xml version="1.0" encoding="UTF-8"?>  

  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.        xsi:schemaLocation="  
  5.            http://www.springframework.org/schema/beans  
  6.            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" default-autowire="byName">  
  7.              
  8.     <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>   
  9.     <import resource="hessian-servlet.xml"/>  
  10.   
  11. </beans>  

    autowire需要爲「byName」。而且需要指定mapping方式爲「BeanNameUrlHandlerMapping」。那麼有spring公佈的hessian服務。其beanName就可以做爲servlet url的一部分。

 

    3. hessian-servlet.xml(Server端)

Java代碼   收藏代碼
  1. <bean id="helloService" class="com.test.remote.impl.HelloServiceImpl" />  
  2. <bean name="/userService" class="org.springframework.remoting.caucho.HessianServiceExporter">  
  3.   <property name="service" ref="userService"/>  
  4.   <property name="serviceInterface" value="com.test.remote.HelloService"/>  
  5. </bean>  

 

    4. client服務配置

Java代碼   收藏代碼
  1. <!-- autowire: byName -->  
  2. <bean id="helloService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">    
  3.     <property name="serviceUrl" value="http://localhost:8080/hessian/helloService" />    
  4.     <property name="serviceInterface" value="com.test.remote.HellowService" />  
  5.     <property name="overloadEnabled" value="true"/>  
  6. </bean>   

    此後,開發人員就可以像使用其它spring bean同樣使用helloService。

相關文章
相關標籤/搜索