Hessian入门案例和源码浅析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ditto_zhou/article/details/84655409

一、简介

二、使用

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

猜你喜欢

转载自blog.csdn.net/Ditto_zhou/article/details/84655409