本文主要介绍两个案例,第一个是使用Hessian来实现远程过程调用,第二个是通过Hessian提供的二进制RPC协议进行和Servlet进行数据交互,Hessian本身即是基于Http的RPC实现。
案例一:
1.准备工作
这里建立一个Maven项目,其中包含四个模块,父模块(仅用来聚合其它模块,不做实际使用),服务器端模块,客户端模块,API模块(远程过程接口,供服务器和客户端使用)。目录结构见下图:
2.添加Hessian的依赖
由于客户端和服务器都要依赖Hessian的包,这里可以添加到父模块的pom.xml中去。
<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.38</version> </dependency>
关于其它的具体依赖配置,这里不做多余展示,本文末尾附有完整的下载地址。
3.在hessian-api模块定义过程接口
package secondriver.hessian.api;
import java.io.InputStream;
import java.util.List;
import secondriver.hessian.api.bean.Person;
public interface HelloHessian {
public String sayHello();
public String sayHello(String name);
public List<Person> getPersons();
public Person getPersonById(int id);
public boolean uploadFile(String fileName, InputStream data);
public byte[] downloadFile(String fileName);
}
上面的接口中定义的六个方法,有重载方法,有获取集合,有获取对象,有上传文件,下载文件等。需要注意的是在Hessian中定义过程接口的方法时输入输出流对象参数应该放置到方法的最后一个参数。另外在实际操作中发现对于流的处理通过远程调用还是会出现一个莫名其妙的问题,加上Hessian缺乏完整的文档(除Hessian的序列号协议)。
关于下载文件这个方法的定义使用了返回byte数组,这将受限与虚拟机的内存或其他因素。网上有说关于文件下载Hessian不支持(未能证实。引申案例二的方式,传输二进制就可以实现下载,不过这将与Hessian的RPC实现无关,而是应用到Hessian的序列号协议),这个可以算是一种策略,So,关于远程文件下载,谁会使用这样的方式呢?!!
4.服务器端实现具体功能
服务器端实现接口
package secondriver.hessian.server.bo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import secondriver.hessian.api.HelloHessian;
import secondriver.hessian.api.bean.Person;
public class HelloHessianImpl implements HelloHessian {
private static Person[] persons = new Person[5];
static {
Random random = new Random();
for (int i = 0, l = persons.length; i < l; i++) {
persons[i] = new Person();
persons[i].setId(i);
persons[i].setGender(random.nextBoolean());
persons[i].setName("name-" + i);
persons[i].setPhone(random.nextLong());
persons[i].setHeight(random.nextDouble());
persons[i].setWeight(random.nextFloat());
persons[i].setAddress(new String[] { "Address" + random.nextInt(),
"Address" + random.nextInt() });
Calendar c = Calendar.getInstance();
c.set(Calendar.DATE, i + 1);
persons[i].setBrithday(c.getTime());
}
}
@Override
public String sayHello() {
return "Hello Hession " + new Date().toString();
}
@Override
public String sayHello(String name) {
return "Welcome " + name;
}
@Override
public List<Person> getPersons() {
return Arrays.asList(persons);
}
@Override
public Person getPersonById(int id) {
for (Person p : persons) {
if (p.getId() == id) {
return p;
}
}
return null;
}
@Override
public boolean uploadFile(String fileName, InputStream data) {
List<String> temp;
try {
temp = IOUtils.readLines(data);
String filePath = System.getProperty("user.dir") + "/temp/"
+ fileName;
FileUtils.writeLines(new File(filePath), temp);
System.out.println("Upload file to " + filePath);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
@Override
public byte[] downloadFile(String fileName) {
String filePath = System.getProperty("user.dir") + "/temp/" + fileName;
InputStream data = null;
try {
data = new FileInputStream(filePath);
int size = data.available();
byte[] buffer = new byte[size];
IOUtils.read(data, buffer);
return buffer;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
IOUtils.closeQuietly(data);
}
}
}
5.服务器端配置远程服务地址
Hessian是Remote On Http工具,服务端是典型的Java Web应用,我们需要在web.xml中配置服务的请求地址。
<servlet> <servlet-name>HelloHessian</servlet-name> <servlet-class>com.caucho.hessian.server.HessianServlet</servlet-class><!-- RPC HessianServlet处理类 --> <init-param> <param-name>home-class</param-name><!-- 远程服务实现类 --> <param-value>secondriver.hessian.server.bo.HelloHessianImpl</param-value> </init-param> <init-param> <param-name>home-api</param-name><!-- 远程服务接口 --> <param-value>secondriver.hessian.api.HelloHessian</param-value> </init-param> </servlet>
6.客户端调用远程对象方法
package secondriver.hessian.client;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.List;
import org.apache.commons.io.IOUtils;
import secondriver.hessian.api.HelloHessian;
import secondriver.hessian.api.bean.Person;
import com.caucho.hessian.client.HessianProxyFactory;
/**
* Hessian RPC
*/
public class HelloHessianClient {
public static String urlName = "http://localhost:8080/hessian-server/HelloHessian";
public static void main(String[] args) throws MalformedURLException {
HessianProxyFactory factory = new HessianProxyFactory();
// 开启方法重载
factory.setOverloadEnabled(true);
HelloHessian helloHession = (HelloHessian) factory.create(
HelloHessian.class, urlName);
// 调用方法
System.out.println("call sayHello():" + helloHession.sayHello());
System.out.println("call sayHello(\"Tom\"):"
+ helloHession.sayHello("Tom"));
System.out.println("call getPersons():");
// 调用方法获取集合对象
List<Person> persons = helloHession.getPersons();
if (null != persons && persons.size() > 0) {
for (Person p : persons) {
System.out.println(p.toString());
}
} else {
System.out.println("No person.");
}
// 通过参数调用方法获取对象
int id = 2;
System.out.println(String.format("call getPersonById(%d)", id));
Person person = helloHession.getPersonById(id);
if (null != person) {
System.out.println(person.toString());
} else {
System.out.println("Id is " + id + " person not exist.");
}
// 上传文件
String fileName = "upload.txt";
String filePath = System.getProperty("user.dir") + "/temp/" + fileName;
InputStream data = null;
try {
data = new BufferedInputStream(new FileInputStream(filePath));
if (helloHession.uploadFile(fileName, data)) {
System.out.println("Upload file " + filePath + " succeed.");
} else {
System.out.println("Upload file " + filePath + " failed.");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(data);
}
// 下载文件
fileName = "download.txt";
filePath = System.getProperty("user.dir") + "/temp/" + fileName;
try {
byte[] temp = helloHession.downloadFile(fileName);
if (null != temp) {
FileWriter output = new FileWriter(filePath);
IOUtils.write(temp, output, "UTF-8");
System.out.println("Download file " + filePath + " succeed.");
output.close();
} else {
System.out.println("Download file " + filePath + " failed.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过客户端调用可以看出,远程方法的具体实现对于客户端来说是透明的。
7.运行程序
7.1启动服务器
7.2运行客户端程序
客户端请求远程服务对象方法调用结果:
call sayHello():Hello Hession Sat Jan 10 12:31:21 CST 2015
call sayHello("Tom"):Welcome Tom
call getPersons():
Person [id=0, gender=true, name=name-0, brithday=Thu Jan 01 12:31:21 CST 2015, height=0.04756537639163749, weight=0.24559122, phone=1359341198036930408, address=[Address2145973789, Address486043733]]
....省略
call getPersonById(2)
Person [id=2, gender=false, name=name-2, brithday=Sat Jan 03 12:31:21 CST 2015, height=0.07169451440704722, weight=0.8515671, phone=2732762596557481818, address=[Address8744397, Address-1690316438]]
Upload file F:\__eclipse\tomsworkspace\hessian-L-parent\hessian-client/temp/upload.txt succeed.
Download file F:\__eclipse\tomsworkspace\hessian-L-parent\hessian-client/temp/download.txt succeed.
案例二:
1. 客户端通过Hessian序列号协议序列化对象,通过Http Post方式提交到服务器端,然后通过反序列化服务器端响应数据。
package secondriver.hessian.data;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Date;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import secondriver.hessian.api.bean.Person;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
public class Test3 {
public static String urlName = "http://localhost:8080/hessian-server/PostDataServlet";
public static void main(String[] args) throws Throwable {
// 序列化
ByteArrayOutputStream os = new ByteArrayOutputStream();
Hessian2Output h2o = new Hessian2Output(os);
h2o.startMessage();
h2o.writeObject(getPerson());
h2o.writeString("I am client.");
h2o.completeMessage();
h2o.close();
byte[] buffer = os.toByteArray();
os.close();
ByteArrayEntity byteArrayEntity = new ByteArrayEntity(buffer,
ContentType.create("x-application/hessian", "UTF-8"));
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost(urlName);
post.setEntity(byteArrayEntity);
CloseableHttpResponse response = client.execute(post);
System.out.println("response status:\n"
+ response.getStatusLine().getStatusCode());
HttpEntity body = response.getEntity();
InputStream is = body.getContent();
Hessian2Input h2i = new Hessian2Input(is);
h2i.startMessage();
Person person = (Person) h2i.readObject();
System.out.println("response:\n" + person.toString());
System.out.println(h2i.readString());
h2i.completeMessage();
h2i.close();
is.close();
}
public static Person getPerson() {
Person person = new Person();
person.setAddress(new String[] { "Beijing", "TaiWan", "GuangZhou" });
person.setBrithday(new Date());
person.setGender(false);
person.setHeight(168.5D);
person.setId(300);
person.setName("Jack");
person.setPhone(188888888);
person.setWeight(55.2F);
return person;
}
}
2.服务器端通过Hessian序列化协议反序列化对象,通过HttpServletResponse对请求进行响应,响应数据是Hessian序列化的二进制结果。
package secondriver.hessian.server.servlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import secondriver.hessian.api.bean.Person;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
public class PostDataServlet extends HttpServlet {
private static final long serialVersionUID = -4461061053732328507L;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 处理请求
ServletInputStream sis = req.getInputStream();
Hessian2Input h2i = new Hessian2Input(sis);
h2i.startMessage();
Person person = (Person) h2i.readObject();
System.out.println("receive:\n" + person.toString());
System.out.println(h2i.readString());
h2i.completeMessage();
h2i.close();
sis.close();
// 发送响应
resp.setCharacterEncoding("UTF-8");
resp.setContentType("x-application/hessian");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Hessian2Output h2o = new Hessian2Output(bos);
h2o.startMessage();
h2o.writeObject(getPerson());
h2o.writeString("I am server.");
h2o.completeMessage();
h2o.close();
ServletOutputStream sos = resp.getOutputStream();
sos.write(bos.toByteArray());
sos.flush();
bos.close();
sos.close();
}
public static Person getPerson() {
Person person = new Person();
person.setAddress(new String[] { "ShangHai", "ShenZhen", "ChengDu" });
person.setBrithday(new Date());
person.setGender(true);
person.setHeight(178.5D);
person.setId(301);
person.setName("Tom");
person.setPhone(188218888);
person.setWeight(55.2F);
return person;
}
}
3.在web.xml中配置PostDataServlet,此处略去具体配置信息,可以参考上面HelloHessian Servlet配置。
4.运行程序
4.1启动服务器
4.2运行客户端程序Test3.
运行结果:
服务器端输出:
[INFO] Restart completed at Sat Jan 10 12:34:51 CST 2015
receive:
Person [id=300, gender=false, name=Jack, brithday=Sat Jan 10 12:35:03 CST 2015, height=168.5, weight=55.2, phone=188888888, address=[Beijing, TaiWan, GuangZhou]]
I am client.
客户端输出:
response status:
200
response:
Person [id=301, gender=true, name=Tom, brithday=Sat Jan 10 12:35:03 CST 2015, height=178.5, weight=55.2, phone=188218888, address=[ShangHai, ShenZhen, ChengDu]]
I am server.
分析输出结果可见Hessian序列化对象通过Http的方式传输,并成功反序列化。
关于Hessian序列号协议可以参见: http://hessian.caucho.com/doc/hessian-serialization.html
写在最后:Hessian的相关资料网上还是比较少,而且例子不尽相同,而且 官方文档 错误之处清晰可见(仔细阅读方可一览无余),本文从Hessian的RPC使用和Hessian序列化和反序列化对象两个方面提供了示例,完整示例下载地址可见本文附件:HessionRPC示例(Eclipse Luna版工程,需要Mavn支持),另外带有源码的Hessian的API文档下载地址: http://down.51cto.com/data/1973896 。
一次不太愉快的Hessian体验使得对RPC的理解更加深刻,无论何种框架,对象的序列化和反序列化,数据的传输协议都是实现RPC的工作重点。