本文是小弟的技术文章处女作,由于个人水平和文字表达能力等原因,错误及不妥之处还望各位同行指点,我的主要目的是把自己的研究心得拿出来分享并使自己有一个总结的过程,高手们见笑了。
Buffalo是一个贯穿前后端的比较完整的Ajax框架,Buffalo最有价值之处,个人感觉有两点:
1、 后端实现了较为完整的基于xml的xml<->object序列化反序列化协议;
2、 前端提供了适配协议的调用封装和响应解析机制,并基于回调机制提供编码API。
这里先给大家详细介绍 buffalo2.0 中JAVA端如何根据 js 提交的请求XML报文来调用最合适的方法。先来看看JS通过buffalo
异步调用java方法的请求格式,比如我要调用 TestSum 类的 sum(double a,double b) 方法,比如我要调用 TestSum 类的 sum(double a,double b) 方法,参数值为1,2,那么在js中的写法是:
buffalo.remoteCall("TestSum.sum",[1,2], function(reply) { // 回调函数,这里buffalo是一个Buffalo对象 }
那么经过buffall.js 处理后的格式为:
1. url: contextPath/buffalo/TestSum ,其中 contenPath是工程路径,buffalo是一个Servlet,作用类似struts中的
ActionSerlvet,而 TestSum 就是类名,其实这只是一个 key ,buffalo 会根据 TestSum 这个 key 到配置文件
buffalo.properties 中查找匹配的类全限名. (其实是从缓存中的HashMap中取,因为buffalo会先读取配置到HashMap中并缓存在ServletContext中)这条配置类似这样 TestSum = com.test.TestSum
2. 方法名和参数经buffalo.js处理后为一XML格式,封装在 http 的请求体中提交到java端由buffalo解析,格式如下:
<buffalo-call>
<method>sum</method> // 这是要调用的方法
<double>1</double> // 之后的都是参数,这里表示有两个参数,均为double,一个值为1,一个为2
<double>2</double>
</buffalo-call>
知道了请求格式,那么接下来就来看看JAVA部分对请求流和URL的处理,是如何解析并调用正确的JAVA方法的。由于本文关注的是如何根据解析出的类名,方法名,参数类型,参数值来调用最合适的方法,所以 buffalo 中较为复杂的部分,即 如何解析 xml,完成 js 到 java 对象的转换这一步将放到下一篇文章中进行介绍,这次只需要知道xml格式的请求信息通过buffalo解析后返回一个BuffaloCall对象,该对象其实就是对方法名,方法参数类型,方法参数值的封装,源码如下:
public class BuffaloCall {
private String methodName; // 方法名,对应例子中的 sum
private Object[] arguments; //方法参数值的集合,对应例子中的 1 , 2
private Class[] argumentTypes; // 方法参数类型的集合,对应例子中的 java.lang.Double , java.lang.Double
public BuffaloCall(String methodName, Object[] arguments) {
this.methodName = methodName;
this.arguments = arguments;
Class[] types = new Class[this.arguments.length];
for (int i = 0; i < arguments.length; i++) {
types[i] = arguments[i].getClass();
}
this.argumentTypes = types;
}
其余的 getter,setter, toString()方法我就不贴出来了,到此,前台的请求信息全部被封装在 BuffalCall对象之中了,大
家可以对比下BuffaloCall类和<buffalo-call>,其实BuffaloCall对象就是对这段xml信息的封装,只不过相应的js类型已经被转
换为JAVA类型了,接下来进入本次主题的核心代码讲解。
如果我们得到一个类的 Class 对象,那么我们就可以通过反射机制来获取其所有方法,如果知道了要调用的方法名,那么我
们就可以通过 Method 的invoke 反射调用该方法,但是有一个问题,如果遇到重载的方法呢 ? 比如 TestSum 类中出现了多个重载且参数个数相同的 sum() 方法sum(int a,int b) ,sum(double a,double b), sum(int a,double b),sum(int[] a,int[] b) 等等类似这样的,我们来看看 buffalo 中是如何根据方法参数的匹配权重来获取最合适的调用方法。
private Map methodCache = new HashMap();
/* service就是通过反射创建的请求对象,这里就是 TestSum对象;
call 就是上面说到的封装了请求方法,参数值,参数类型的BuffaloCall对象;
signature 这里可以先看做为一个 HashMap的 key 对象,因为buffalo会将本次请求的方法缓存在HashMap中,如果下次同样请求调用这个方法,就不用重复查找了,因为反射会降低效率嘛
*/
private Method lookupMethod(Object service, BuffaloCall call,
Signature signature) {
Method[] methods = service.getClass().getMethods(); //取得调用对象(这里为TestSum)的所有方法集合
List matchedResults = new ArrayList(); // 这个List存储所有和请求方法同名的方法对象
for (int i = 0; i < methods.length; i++) { // 循环调用对象的每个方法
Method method = methods[i];
// 对与请求方法同名的做进一步操作
if (method.getName().equals(call.getMethodName())) {
Class[] parameterTypes = method.getParameterTypes(); // 获取方法的参数类型集合
int matched = 0, weight = 0;
//如果方法参数个数与请求方法的参数个数相同,则进一步操作
if (parameterTypes.length == call.getArguments().length) {
for (int j = 0; j < parameterTypes.length; j++) {
// 循环获取方法的每个参数类型的匹配权重并累加在变量 weight 中
int matchWeight = parameterAssignable(
parameterTypes[j], call.getArguments()[j].getClass());
if (matchWeight > 0) {
matched++; //参数每匹配成功一次,就递增 matched
weight += matchWeight;
}
}
}
// 如果参数都匹配成功的话,就将这个方法对象和权重封装到对象 MatchedResult中去
if (matched == parameterTypes.length) {
matchedResults.add(new MatchedResult(weight, method));
}
}
}
if (matchedResults.size() == 0) { // 没有匹配的方法,返回 null
return null;
} else if (matchedResults.size() == 1) { // 只有一个匹配则先缓存结果然后直接返回匹配的Method对象
methodCache.put(signature, ((MatchedResult) matchedResults.get(0))
.getMethod());
return ((MatchedResult) matchedResults.get(0)).getMethod();
} else {
/* 存在多个匹配的方法,那么根据前面计算的权重排序,权重最高者为最精确匹配的方法,MatchedResult实
现了Comparable 接口,覆盖了 compareTo方法,该方法根据 权重大小 weight 进行比较判断
*/
Collections.sort(matchedResults);
// 经过排序后,matchedResults这个List中的第一个MatchedResult元素就包含了最精确匹配的
// Method字段,所以这里就取第一个MatchedResult元素的Method缓存起来并返回。
methodCache.put(signature, ((MatchedResult) matchedResults.get(0)).getMethod());
return ((MatchedResult) matchedResults.get(0)).getMethod();
}
}
private int parameterAssignable(Class targetType, Class sourceType) {
if (targetType.equals(sourceType)) return 6; //如果类型相等,匹配度为6
if (targetType.isAssignableFrom(sourceType)) return 5; //目标参数类型是请求参数类型的超类或超接口,为5
if (targetType.isPrimitive()) { //如果是primitive,则获取Wrapper类
targetType = ClassUtil.getWrapperClass(targetType);
}
if (targetType.equals(sourceType)) {
return 4; //此时肯定是经过处理的匹配,只能为4
} else if (Number.class.isAssignableFrom(targetType) &&
Number.class.isAssignableFrom(sourceType)) {
return 3; //如果两边都是Number型的,马虎匹配了,为3
}
return 0; //0就是没啥匹配度了
}
到此为止,已经找到了最精确匹配的方法了,之后就是通过反射来调用这个方法。在调用这个方法之前buffalo还有做一些其
他处理,但和本文的主题没啥关系,且和解析xml有关,所以这里就先不讲了。
本文介绍的虽然不算复杂,但我觉得其处理思路还是值得借鉴的,下一章将介绍 buffalo 最核心的部分即对请求报文
xml的解析并转换为正确的java类型部分,这里涉及的知识点和类很多,需要我好好整理段时间,其中模板方法模式涉及较多,且
我将绘制些 uml静态图和时序图辅助,所以请不熟悉的这些基础知道的朋友先了解些模板方法模式的运用场景和uml基础。
如果我发觉自己哪里写的不对需要改进,我会及时更新本文的,有兴趣的朋友可以关注下。