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。

相关文章
相关标签/搜索