我们知道,大多数情况下,方法的调用都是发生在相同堆上的两个对象之间,所有组件都在同一台计算机的同一个Java虚拟机的同一个堆空间上执行是最简单的,如果用户端只是个能够执行Java的装置怎么办?如果为了安全性的理由只能让服务器上的程序存取数据库怎么办?例如,我们的BS系统、单纯的CS系统等等,但是如果我们有这样的需求:我们的项目需要调用不同机器上的对象和方法,我们应该怎么处理呢,例如下面的案例:
大家都知道,我们的web项目在服务器上部署完后,我们可以通过在浏览器*问地址的方式进行访问,也可以通过在地址栏输入地址的方式执行里面的方法:http://localhost:8080/test/query!queryUser.do,然后在页面上处理得到我们相应的结果,如果我们的客户端不是使用浏览器访问,而是通过swing程序或者其他非浏览器访问方式,我们要在客户端程序中获取服务器里面相应的方法,得到相应的信息后,再在我们的客户端做出相应的逻辑处理,我们的客户端不与数据库打交道,无需操心将数据发送到网络上或者解析响应之类的问题,我们该如何进行处理呢?
这就用到了我们的一种远程调用机制,我们从某一台计算机上面取得另一台计算机上的信息是通过socket的输入/输出流,打开另一台计算机的socket连接,然后取得outputStream来写入数据.但如果要调用另一台计算机上,另一个Java虚拟机上面的对象的方法,我们当然可以自己定义和设计通信协议来调用,然后通过Socket把执行结果再传回去,并且还能够像是对本机的方法调用一样,也就是说想要调用远程的对象(像是别的堆上的),却又要像是一般的调用,这就是分布式的服务调用,在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java底层领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、SOAP、EJB和JMS 等,在j2ee中,对java底层远程通讯的技术进行了封装,形成了 Hessian 、 HttpInvoker 、 XFire 、 Axis 等多种形式的远程调用技术。但对高级程序员而言仍需要掌握Java底层领域中远程通讯的技术,尤其是rmi,xml-rpc,JMS。下面我来给大家讲解一下由caucho提供的一个基于binary-RPC实现的远程通讯library的Hessian。
Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
<!--以上很大部分摘自网络,自己整合-->
说到底,就是我们要通过一台机器调用另一台机器上、另一个java虚拟机上的对象和方法,我们可以通过轻量级的远程通讯Hessian来实现,下面我们来搭建一个简单的helloworld:
使用Hessian,需要导入Hessian的一个支持包,可以在它的官网进行下载:http://hessian.caucho.com,也可以在我的资源库里面进行免费下载:http://download.csdn.net/detail/harderxin/7125443
好了,下面让我们开始使用Hessian,我要实现的功能是,我的客户端能够通过main方法调用服务器端的方法,并且将服务器端执行的结果返回给客户端打印出来:
一、新建web项目,我取名为HessianServer,然后将hessian-4.0.37.jar导入我们的工程中,然后建立我们的实体类User,因为我客户端就是要获取User信息,给他定义id、name、password属性:
package com.server.bean; import java.io.Serializable; public class User implements Serializable{ /** * */ private static final long serialVersionUID = 7175134832651443717L; //用户编号 private int id; //用户名 private String userName; //密码 private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public User(int id, String userName, String password) { super(); this.id = id; this.userName = userName; this.password = password; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((userName == null) ? 0 : userName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (id != other.id) return false; if (password == null) { if (other.password != null) return false; } else if (!password.equals(other.password)) return false; if (userName == null) { if (other.userName != null) return false; } else if (!userName.equals(other.userName)) return false; return true; } }
定义一个UserService接口,用来处理User逻辑,也是得交给客户端去动态代理的:
package com.server.service; import java.util.List; import com.server.bean.User; public interface UserService { public List<User> getUser(); }
定义UserService的实现类UserServiceImpl,该类实现UserService,并处理相应的逻辑:
package com.server.service.impl; import java.util.ArrayList; import java.util.List; import com.server.bean.User; import com.server.service.UserService; public class UserServiceImpl implements UserService{ public List<User> getUser() { //我们可以在这个方法中与数据库打交道 List<User> list=new ArrayList<User>(); list.add(new User(1,"Mary","123456")); list.add(new User(2,"Jack","236547")); list.add(new User(3,"Joy","362541")); return list; } }
二、配置好web.xml,将Hessian与我们的项目进行整合:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <servlet> <servlet-name>UserService</servlet-name> <!-- 配置HessianServlet--> <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class> <init-param> <param-name>home-class</param-name> <!-- 我们定义的接口实现类 --> <param-value>com.server.service.impl.UserServiceImpl</param-value> </init-param> <init-param> <param-name>home-api</param-name> <!-- 我们定义的接口类 --> <param-value>com.server.service.UserService</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>UserService</servlet-name> <!-- 客户端访问路径 --> <url-pattern>/us</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
三、将User和UserService打成的jar包,让客户端能够得到服务端的实体和接口,我们也可以不用这样做,但是我们得需要把服务器端的User和UserService类再复制到客户端,所以做成jar包方便我们的访问,我打的jar包名称为:project-1.0.jar
好了,我们的服务端弄好了,下面我们将服务端部署到Tomcat服务器上,然后启动tomcat服务器,我们程序的访问地址为:http://localhost:8080/HessianServer/us
四、编写我们的客户端,我取名为HessianClient,将hessian-4.0.37.jar和我们刚刚导出的project.1.0.jar导入到我们的工程中,新建一个UserServiceTest的java类:
package com.client.test; import java.net.MalformedURLException; import java.util.List; import com.caucho.hessian.client.HessianProxyFactory; import com.server.bean.User; import com.server.service.UserService; public class UserServiceTest { public static void main(String[] args) { //我们的服务器访问的地址 String url="http://localhost:8080/HessianServer/us"; //获得HessianProxyFactory实例 HessianProxyFactory factory=new HessianProxyFactory(); try { //创建我们的接口对象 UserService userService=(UserService)factory.create(url); //执行服务端方法 List<User> users=userService.getUser(); //遍历输出 for(User user:users){ System.out.println("id="+user.getId()+",name="+user.getUserName()+",pwd="+user.getPassword()); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
执行main方法,打印出来得到的结果为:
id=1,name=Mary,pwd=123456
id=2,name=Jack,pwd=236547
id=3,name=Joy,pwd=362541
通过Hessian,我们成功的从我们的客户端(PC机1)访问到了服务器(PC机2)上的方法和对象,两个程序的运行在两个不同的java虚拟机上面,这就是一个很简单的远程通讯,下面来带大家更多的了解Hessian:
1、是基于什么协议实现的?
基于Binary-RPC协议实现。
2、怎么发起请求?
需通过Hessian本身提供的API来发起请求。
3、怎么 将请求转化为符合协议的格式的?
Hessian通过其自定义的串行化机制将请求信息进行序列化,产生二进制流。
4、使用什么 传输协议传输?
Hessian基于Http协议进行传输。
5、响应端基于什么机制来接收请求?
响应端根据Hessian提供的API来接收请求。
6、怎么将流还原为传输格式的?
Hessian根据其私有的串行化机制来将请求信息进行反序列化,传递给使用者时已是相应的请求信息对象了。
7、处理完毕后怎么回应?
处理完毕后直接返回,hessian将结果对象进行序列化,传输至调用端。
简单的原理图:
1)Hessian远程访问基于序列化和反序列化的方式。当程序运行时,程序所创建的各种对象都位于内存中,当程序运行结束,这些对象就结束了生命周期。对象的序列化主要有两种用途:
l 把对象的字节序列永久地保存到硬盘上,通常是放在一个文件中。
l 在网络上传输对象的字节序列
Hessian就是把Java对象转变成 字节序列,然后通过Http传输到 目标服务器上,目标服务器收到这个字节序列后,按照一定的协议标准进行反序列,提交给对应的服务处理。处理完成以后以同样的方式返回数据\
2)配置的Servlet: com.caucho.hessian.server.HessianServlet
对应的参数:接口(home-api):com.alisoft.enet.hessian.Hello
实现(home-class): com.alisoft.enet.hessian.HelloImpl
HessianServlet 中的实现代码如下(略过部分代码):
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
//输入流
Hessian2Input in = new Hessian2Input(is);
SerializerFactory serializerFactory = getSerializerFactory();
in.setSerializerFactory(serializerFactory);
//输出流
AbstractHessianOutput out;
int major = in.read();
int minor = in.read();
out = new Hessian2Output(os);
out.setSerializerFactory(serializerFactory);
_homeSkeleton.invoke(in, out);
整个执行步骤如下:
l 接收输入流,并通过SerializerFactory转化为 Hessian 特有的 Hessian2Input
l 设置输出流,并通过SerializerFactory转化为 Hessian 特有的 Hessian2Output
l 根据配置的接口和实现参数,调用服务,并把结果写入到输出流 Hessian2Output中
l Out.close()
3)Hessian 和其他远程调用实现的比较
1. 常见远程通讯协议:
RMI 、 Httpinvoker 、 Hessian 、 Burlap 、 Web service
通讯效率测试结果:
RMI > Httpinvoker >= Hessian >> Burlap >> Web service
2. 各个通讯协议的分析:
RMI 是 Java 首选远程调用协议,非常高效稳定,特别是在数据结构复杂,数据量大的情况下,与其他通讯协议的差距尤为明显。
HttpInvoker 使用 java 的序列化技术传输对象,与 RMI 在本质上是一致的。从效率上看,两者也相差无几, HttpInvoker 与 RMI 的传输时间基本持平。
Hessian 在传输少量对象时,比 RMI 还要快速高效,但传输数据结构复杂的对象或大量数据对象时,较 RMI 要慢 20% 左右。但这只是在数据量特别大,数据结构很复杂的情况下才能体现出来,中等或少量数据时, Hessian 并不比 RMI 慢。 Hessian 的好处是精简高效,可以跨语言使用,而且协议规范公开,我们可以针对任意语言开发对其协议的实现。
另外, Hessian 与 WEB 服务器结合非常好,借助 WEB 服务器的成熟功能,在处理大量用户并发访问时会有很大优势,在资源分配,线程排队,异常处理等方面都可以由成熟的 WEB 服务器保证。而 RMI 本身并不提供多线程的服务器。而 且, RMI 需要开防火墙端口, Hessian 不用。
Burlap 采用 xml 格式传输。仅在传输 1 条数据时速度尚可,通常情况下,它的毫时是 RMI 的 3 倍。
Web Service 的效率低下是众所周知的,平均来看, Web Service 的通讯毫时是 RMI 的 10 倍。
至于RMI、Web service不了解的可以自己在网上下载相应的文档进行查看学习
4)分布式编程的定义和理解:
基本思想
所有分布编程技术的基本思想都很简单:
1.客户端通过网络将请求发送到服务端.
2.服务端处理请求返回相应.
3.客户端完成后续处理.
定义声明:
这些请求和响应不是在Web 应用程序中看到的请求和响应,这里的客户端并非是Web浏览器,它可以是执行具有任意复杂度业务规则的任何应用程序。(例如Swing用户界面等等)用于请求和响应数据的协议允许传递任意对象,而传统的Web应用程序只限于对请求使用Http协议,对响应使用HTML.
我们需要什么样的分布式模式?
开发中我们正真需要的这样一种机制:客户端程序员以常规的方式进行方法调用,而无需操心将数据发送到网络上或者解析响应之类的问题。解决方法:在客户端为远程对象安装一个代理(Proxy)。代理是位于客户端虚拟机中的一个对象,对于客户端程序来说,看起来就像是要访问的远程对象一样。客户调用此代理时,只需进行常规的方法调用。而客户端代理负责使用网络协议与服务进行联系。 同样,服务端的程序员也不希望因与客户端之间的通信被绊住。解决方法:在服务端也安装一个代理。该服务端代理与客户端代理进行通信,并且它将以常规方式调用服务器对象上的方法。
代理之间是如何通信的呢?通常有三种选择:
Corba:通用的对象请求代理架构,支持任何语言编写对象之间的方法调用。
Corba使用二进制的Internet Inter- ORB协议(IIOP)来实现对象间的通信.
Web:Web服务架构是一个协议集,有时统一描述为WS-*它也是独立于编程语言的,不过它使用基于XML的通信格式.用于传输对象的格式则是简单对象访问协议.
RMI:Java的远程方法调用技术,支持Java的分布式对象之间的方法调用.
分布式编程的关键是远程方法调用。在一台计算机上(客户端)上的某些代码希望调用另一台计算机(服务端)上的某个对象的一个方法.要实现这一点,方法的参数必须以某种方式传递到另外一台机器上,而服务器必须得到通知,去定位远程对象并执行要调用的方法,并且必须将返回值传递回去.
方法调用的关键元素:存根与参数编组.当客户端代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通的方法,我们称此代理对象为存根(stub).
存根位于客户端机器上,而非服务器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程称作参数编组,参数编组的目的是将参数转换为适合在虚拟机之间进行传递的格式.获取客户端存根的细节依赖采用的具体分布式技术.
Hessian和Spring、hibernate、Struts整合见下篇博文!欢迎一起交流学习