Hessian RPC示例和基於Http請求的Hessian序列化物件傳輸
本文主要介紹兩個案例,第一個是使用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的相關資料網上還是比較少,而且例子不盡相同,而且 官方文件 錯誤之處清晰可見(仔細閱讀方可一覽無餘),本文從Hessian的RPC使用和Hessian序列化和反序列化物件兩個方面提供了示例,完整示例下載地址可見本文附件:HessionRPC示例(Eclipse Luna版工程,需要Mavn支援),另外帶有原始碼的Hessian的API文件下載地址: http://down.51cto.com/data/1973896 。
一次不太愉快的Hessian體驗使得對RPC的理解更加深刻,無論何種框架,物件的序列化和反序列化,資料的傳輸協議都是實現RPC的工作重點。