在使用Hessian框架时,我们将Hessian分为客户端client和服务端server,这两个名称会频繁的出现在该系列的博客中
使用Hessian时,server端必须是依赖于容器的(比入tomcat,jetty或者自己实现的可以解析HTTP请求的容器),客户端则没有限制(因为Hessian帮助我们创建了HTTP的连接),下面我们开始分析Hessian的源码。
先来看一个最简单的Hessian客户端的代码
public static void main(String[] args) {
String url="http://localhost:8080/service/remote";
HessianProxyFactory factory = new HessianProxyFactory();
HessianHelloWorld hello = null;
try {
hello = (HessianHelloWorld) factory.create(HessianHelloWorld.class,url);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Person p = new Person();
hello.helloTo(p);
System.out.println(hello.disp("whoareyou","konglingqiu",29));
}
HessianProxyFactory是Hessian的核心类之一,对外(使用人员)提供了创建远程代理对象的create()方法,对内提供了建立远程连接的工厂和序列化工厂
创建远程代理对象
通过将远程的将要调用的方法的class对象和该对象所在的远程的路径create()方法会创建一个代理对象,一旦获得了代理对象后我们就可以像调用一个本地方法那样去调用一个远程的方法。java动态代理相关的内容不在本系列博客的讨论范围之内(不懂java动态代理不会影响我们分析Hessian源码),理解了代理对象是如何将本地方法调用传输到了server端对我们理解Hessian框架是至关重要的。下面我们来看看HessianProxyFactory是如何帮助我们创建代理对象的。
在下面代码段中的①处创建了真正的代理实例HessianProxy(上面的一堆代码都是做一些检验,缓存和线程安全用的,不属于核心代码,在分析源码的过程中首先要抓住主干,不要被细枝末节所干扰,主干搞明白了再来看细节),然后放到了缓存proxyMap中,最后返回,这样所有的方法调用都会经过HessianProxy对象的invoke()方法
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;
if(proxyMap.get(url)!=null){
return proxyMap.get(url);
}
synchronized (this) {
if(proxyMap.get(url)!=null){
return proxyMap.get(url);
}
handler = new HessianProxy(url, this, api);//①
Object proxy=Proxy.newProxyInstance(loader,new Class[] { api,HessianRemoteObject.class },handler);
proxyMap.put(url, proxy);
}
return proxyMap.get(url);
}
这里我们要注意一下,在使用过程中不要创建过多的HessianProxyFactory,只要创建一个就够了,因为每一个url对应一个远程的接口同时对应着一个HessianProxy代理对象,url,远程接口,HessianProxy代理对象是一一对应的关系。
现在client端拿到了远程代理,就可以像调用普通方法一样来调用远程方法了,接下来让我们看看代理对象HessianProxy的invoke()方法帮助我们做了哪些事情
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();
System.out.println("HessianProxy:invoke:methodName="+methodName);
Class<?> []params = method.getParameterTypes();
for(Class<?> cla:params){
System.out.println("HessianProxy:invode:params="+cla.getCanonicalName());
}
// equals and hashCode are special cased
if (methodName.equals("equals")
&& params.length == 1 && params[0].equals(Object.class)) {
Object value = args[0];
if (value == null || ! Proxy.isProxyClass(value.getClass()))
return Boolean.FALSE;
Object proxyHandler = Proxy.getInvocationHandler(value);
if (! (proxyHandler instanceof HessianProxy))
return Boolean.FALSE;
HessianProxy handler = (HessianProxy) proxyHandler;
return new Boolean(_url.equals(handler.getURL()));
}
else if (methodName.equals("hashCode") && params.length == 0)
return new Integer(_url.hashCode());
else if (methodName.equals("getHessianType"))
return proxy.getClass().getInterfaces()[0].getName();
else if (methodName.equals("getHessianURL"))
return _url.toString();
else if (methodName.equals("toString") && params.length == 0)
return "HessianProxy[" + _url + "]";
if (! _factory.isOverloadEnabled())
mangleName = method.getName();
else
mangleName = mangleName(method);
synchronized (_mangleMap) {
_mangleMap.put(method, mangleName);
}
}
InputStream is = null;
HessianConnection conn = null;
try {
if (log.isLoggable(Level.FINER))
log.finer("Hessian[" + _url + "] calling " + mangleName);
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 {
try {
if (is != null)
is.close();
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
try {
if (conn != null)
conn.destroy();//④======================
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
}
}
在代码①处创建了远程代理连接,并把调用远程对象的方法名称和参数传到了sendRequest()中,sendRequest()方法负责将具体的请求信息转换成二进制流发送到远程server端,server端收到二进制流后进行解析并调用server端对应的方法,最后把结果转换成二进制流再返回给client端,(具体细节下篇文章分析),代码②处拿到连接的输入流,代码③处从输入流中解析出返回码,③下面的代码负责解析出返回信息作为远程方法调用的返回值,最后在代码④处finally块内关闭了连接,至此,一个远程调用就结束了。
Hessian是基于Http协议来做的远程方法调用,而Http协议是基于TCP/IP协议之上的协议,我们都知道TCP/IP协议是长连接,而Http是短连接,那短协议的HTTP为什么是基于更底层的长连接协议TCP/IP的呢
就是因为在④处手动关闭了TCP连接,每次请求我们都会重新建立连接,请求结束都会关闭连接,这就是为什么HTTP是短连接无状态的协议的原因