1. 程式人生 > >Hessian入門案例和原始碼淺析

Hessian入門案例和原始碼淺析

一、簡介

二、使用

Server 

Client

三、原理

服務端原始碼淺析

1. HessianServlet的init方法,建立介面的類物件和介面的實現類物件,並初始化HessianSkeleton物件。

2. service方法內則是呼叫HessianSkeleton物件的invoke方法處理請求

3. HessianSkeleton的invoke方法

Hessian客戶端原始碼淺析

1. 建立HessianProxyFactory,呼叫create方法獲取到方法結果

2. HessianProxy的核心實現invoke方法

總結


一、簡介

Hessian是一個使用二進位制傳輸的服務框架,一種輕便的RPC框架,是基於HTTP協議的。

因為,Hessian是基於HTTP協議的,那麼就會有一個Web服務(Hessian服務端),消費端通過獲取代理物件來呼叫服務端的方法,

服務端和客戶端都需要依賴介面.

二、使用

  • 公共服務介面類:中間工程jar包middleProject,僅僅包含hello方法和一個重新設定使用者年齡的方法。
  • 服務端:構建成一個web服務,只有一個介面實現類需要依賴middleProject,需要配置成hessian服務。
  • 客戶端:同樣依賴middleProject,使用hessian代理工廠例項化公共介面服務類,然後呼叫該例項的方法。

maven專案作為案例,zy_client是客戶端,zy_server是服務端,zy_interface是介面工程

建立介面

public interface HelloService {
    String sayHello(String str);
}

Server 

server服務端是一個web專案,釋出服務。

1. 引入介面依賴和Hessian依賴

 <dependency>
      <groupId>com.caucho</groupId>
      <artifactId>hessian</artifactId>
      <version>4.0.38</version>
    </dependency>
<!-- 介面的依賴省略... -->

2. 實現介面

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String str) {
        return "hello"+str;
    }
}

3. web.xml 中配置HessianServlet

 <servlet>
    <servlet-name>hessianServlet</servlet-name>
    <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class>
    <init-param>
      <param-name>home-class</param-name>
      <param-value>cn.bing.service.impl.HelloServiceImpl</param-value>
    </init-param>
    <init-param>
      <param-name>home-api</param-name>
      <param-value>cn.bing.service.HelloService</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>hessianServlet</servlet-name>
    <url-pattern>/hessian</url-pattern>
  </servlet-mapping>

4. 將web啟動起來,服務的地址是 http://localhost:8080/hessian

Client

1. 引入介面和Hessian的依賴

 <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.38</version>
  </dependency>
  <!-- 介面的依賴省略  -->

2. 呼叫服務,通過hessian的代理工廠生成helloService例項,注意到如果傳遞的資料是pojo類,需要將pojo實現序列化介面,

本例中是String型別資料。

public class RemoteTest {
    public static void main(String[] args) throws Exception{
        String url = "http://localhost:8080/hello/hessian";
        HessianProxyFactory factory = new HessianProxyFactory();
        HelloService helloService = (HelloService) factory.create(HelloService.class, url);
        String str =  helloService.sayHello("world!");
        System.out.println(str);
    }
}

三、原理

服務端原始碼淺析

1. HessianServlet的init方法,建立介面的類物件和介面的實現類物件,並初始化HessianSkeleton物件。

這個類的父類是AbstractSkeleton,初始化的過程中,會以方法名稱和method物件存入 _methodMap 中,後續接受到請求,從這個map中取出method物件處理。

public void init(ServletConfig config)
    throws ServletException
  {
    super.init(config);
    
    try {
      if (_homeImpl != null) {
      }
      else if (getInitParameter("home-class") != null) {
        String className = getInitParameter("home-class");

        Class<?> homeClass = loadClass(className);

        _homeImpl = homeClass.newInstance();

        init(_homeImpl);
      }
      else if (getInitParameter("service-class") != null) {
        String className = getInitParameter("service-class");

        Class<?> homeClass = loadClass(className);

        _homeImpl = homeClass.newInstance();

        init(_homeImpl);
      }
      else {
        if (getClass().equals(HessianServlet.class))
          throw new ServletException("server must extend HessianServlet");

        _homeImpl = this;
      }

      if (_homeAPI != null) {
      }
      else if (getInitParameter("home-api") != null) {
        String className = getInitParameter("home-api");

        _homeAPI = loadClass(className);
      }
      else if (getInitParameter("api-class") != null) {
        String className = getInitParameter("api-class");

        _homeAPI = loadClass(className);
      }
      else if (_homeImpl != null) {
        _homeAPI = findRemoteAPI(_homeImpl.getClass());

        if (_homeAPI == null)
          _homeAPI = _homeImpl.getClass();
        
        _homeAPI = _homeImpl.getClass();
      }
      
      //..................

      _homeSkeleton = new HessianSkeleton(_homeImpl, _homeAPI);
      
     //................
  }

2. service方法內則是呼叫HessianSkeleton物件的invoke方法處理請求

public void service(ServletRequest request, ServletResponse response)
    throws IOException, ServletException
  {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    if (! req.getMethod().equals("POST")) {
      res.setStatus(500); // , "Hessian Requires POST");
      PrintWriter out = res.getWriter();

      res.setContentType("text/html");
      out.println("<h1>Hessian Requires POST</h1>");
      
      return;
    }

    String serviceId = req.getPathInfo();
    String objectId = req.getParameter("id");
    if (objectId == null)
      objectId = req.getParameter("ejbid");

    ServiceContext.begin(req, res, serviceId, objectId);

    try {
      InputStream is = request.getInputStream();
      OutputStream os = response.getOutputStream();

      response.setContentType("x-application/hessian");

      SerializerFactory serializerFactory = getSerializerFactory();

      invoke(is, os, objectId, serializerFactory);
    } catch (RuntimeException e) {
      throw e;
    } catch (ServletException e) {
      throw e;
    } catch (Throwable e) {
      throw new ServletException(e);
    } finally {
      ServiceContext.end();
    }
  }

3. HessianSkeleton的invoke方法

 public void invoke(InputStream is, OutputStream os,
                     SerializerFactory serializerFactory)

上面的的方法核心的是呼叫它的過載方法處理,這個方法才是Hessian服務端處理請求的核心方法.

方法的主要引數:

serivce: servlet初始化時候就例項化的實現類物件

in : 輸入流,獲取請求中的方法名稱和引數列表

out: 輸出流,將結果寫回到客戶端

public void invoke(Object service,
                     AbstractHessianInput in,
                     AbstractHessianOutput out)
    throws Exception
  {
    ServiceContext context = ServiceContext.getContext();

    // backward compatibility for some frameworks that don't read
    // the call type first
    in.skipOptionalCall();

    // Hessian 1.0 backward compatibility
    String header;
    while ((header = in.readHeader()) != null) {
      Object value = in.readObject();

      context.addHeader(header, value);
    }

    String methodName = in.readMethod();
    int argLength = in.readMethodArgLength();

    Method method;

    method = getMethod(methodName + "__" + argLength);

    if (method == null)
      method = getMethod(methodName);

    if (method != null) {
    }
    else if ("_hessian_getAttribute".equals(methodName)) {
      String attrName = in.readString();
      in.completeCall();

      String value = null;

      if ("java.api.class".equals(attrName))
        value = getAPIClassName();
      else if ("java.home.class".equals(attrName))
        value = getHomeClassName();
      else if ("java.object.class".equals(attrName))
        value = getObjectClassName();

      out.writeReply(value);
      out.close();
      return;
    }
    else if (method == null) {
      out.writeFault("NoSuchMethodException",
                     escapeMessage("The service has no method named: " + in.getMethod()),
                     null);
      out.close();
      return;
    }

    Class<?> []args = method.getParameterTypes();

    if (argLength != args.length && argLength >= 0) {
      out.writeFault("NoSuchMethod",
                     escapeMessage("method " + method + " argument length mismatch, received length=" + argLength),
                     null);
      out.close();
      return;
    }

    Object []values = new Object[args.length];

    for (int i = 0; i < args.length; i++) {
      // XXX: needs Marshal object
      values[i] = in.readObject(args[i]);
    }

    Object result = null;

    try {
      result = method.invoke(service, values);
    } catch (Exception e) {
      Throwable e1 = e;
      if (e1 instanceof InvocationTargetException)
        e1 = ((InvocationTargetException) e).getTargetException();

      log.log(Level.FINE, this + " " + e1.toString(), e1);

      out.writeFault("ServiceException", 
                     escapeMessage(e1.getMessage()), 
                     e1);
      out.close();
      return;
    }

    // The complete call needs to be after the invoke to handle a
    // trailing InputStream
    in.completeCall();

    out.writeReply(result);

    out.close();
  }

invoke的方法處理流程:

1. 從請求中獲取方法名稱和方法的引數

2. 從_methodMap中查出對應的method物件,呼叫method物件的invoke方法處理得到方法的處理結果

3. 輸出流將方法的處理結果寫回客戶端。

Hessian客戶端原始碼淺析

1. 建立HessianProxyFactory,呼叫create方法獲取到方法結果

public Object create(Class<?> api, URL url, ClassLoader loader)
  {
    if (api == null)
      throw new NullPointerException("api must not be null for HessianProxyFactory.create()");
    InvocationHandler handler = null;

    handler = new HessianProxy(url, this, api);

    return Proxy.newProxyInstance(loader,
                                  new Class[] { api,
                                                HessianRemoteObject.class },
                                  handler);
  }

方法內部通過Proxy建立一個代理物件,代理物件的核心是在handler的實現上,看下handler的實現 HessianProxy

public class HessianProxy implements InvocationHandler, Serializable {

代理物件的方法呼叫的核心是呼叫handler的invoke方法,接下來,看下invoke的實現。  

2. HessianProxy的核心實現invoke方法

方法內部以方法名和引數發起了HTTP請求,從請求的結果中獲取到方法的執行結果

客戶端呼叫方法的時候,會去傳送一次HTTP請求,將方法名稱和引數傳遞到服務端,服務端處理好之後,將結果寫回到客戶端。

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 cased
      if (methodName.equals("equals")
          && params.length == 1 && params[0].equals(Object.class)) {
       //....
      }
      else if (methodName.equals("hashCode") && params.length == 0)
      //....
    }

    InputStream is = null;
    HessianConnection conn = null;
    
    try {
      if (log.isLoggable(Level.FINER))
        log.finer("Hessian[" + _url + "] calling " + mangleName);
      //這裡以方法名稱和方法引數,發起一個HTTP請求
      conn = sendRequest(mangleName, args);
      // 從請求中獲取輸入流,再從流中讀取資料
      is = getInputStream(conn);

      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;
        }
        else
          in.completeReply();

        return value;
      }
      else
        throw new HessianProtocolException("'" + (char) code + "' is an unknown code");
    } catch (HessianProtocolException e) {
      throw new HessianRuntimeException(e);
    } finally {
     //....
    }
  }

總結

 Hessian的客戶端的每一次的方法呼叫實際上都是一次HTTP請求。具體來說,客戶端每次呼叫代理物件的方法實際上是以方法名和引數發起HTTP請求到服務端,服務端接受到引數處理後,將結果寫回到客戶端的過程,客戶端從流中獲得處理結果。

參考部落格: https://blog.csdn.net/sunwei_pyw/article/details/74002351