Hessian入門案例和原始碼淺析
1. HessianServlet的init方法,建立介面的類物件和介面的實現類物件,並初始化HessianSkeleton物件。
2. service方法內則是呼叫HessianSkeleton物件的invoke方法處理請求
1. 建立HessianProxyFactory,呼叫create方法獲取到方法結果
一、簡介
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