锁定老帖子 主题:Ajax框架Buffalo深度研究
精华帖 (4) :: 良好帖 (12) :: 新手帖 (0) :: 隐藏帖 (0)
|
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||
发表时间:2010-01-11
最后修改:2010-01-19
Buffalo深度研究 ——2010.01.11, IT进行时[MSN:zhengxianquan AT hotmail Dot com] 目录 1. BUFFALO概述 2
Buffalo是一个贯穿前后端的比较完整的Ajax框架,目前最新的版本是2.0.1,其主页是: http://buffalo.sourceforge.net/,可通过该页面找到下载。 不过该版本自2007年来就没有更新了,有点遗憾,不管怎样,一出来就关注到了,早前通读过代码,是个好作品。 上周开始用了些零碎的时间,重新评估并进行了深入的研究,其目的在于通过深度掌握某个优秀的贯穿前后端的AJAX框架,以提高自己的整体认知感。 略,可参考:http://buffalo.sourceforge.net/features.html
Buffalo最有价值之处,个人感觉有两点: 1、 后端实现了较为完整的基于xml的xml<->object序列化反序列化协议; 2、 前端提供了适配协议的调用封装和响应解析机制,并基于回调机制提供编码API。 另,作品受xstream影响颇深,如果看过xstream的代码大家的感觉会更明显,不知道这样说Michael是否有意见:buffalo后端转换器、IO部分的代码,是xstream的lightweight版本:) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
2. Buffalo的关键序列图要了解buffalo,与其他开源框架一样,最好的实践在于“跑起来”,跟踪并分析其调用执行过程,即可串接其核心的代码组织、逻辑调用关系等细节。 其请求过程的序列图可大概绘制如下:
概要说明: 1、 整体提供了一个Servlet叫ApplicationServlet作为唯一的前后端通讯窗口;通过Servlet的init过程初始化配置暴露给Buffalo远程调用的服务(支持内置的buffalo-service.properties配置及spring配置),置于ServiceRepository中; 2、 根据request.getPathInfo()所得到的URL规则,如“/buffalo/simpleService”,创建对应的Worker,目前仅实现了BuffaloWorker; 3、 同样根据pageInfo解析服务名称(如simpleService),并从ServiceRepository中获取对应的服务实例; 4、 通过BuffaloProtocal实例unmarshall客户端发出的InputStream,并通过获取方法和参数,构建完整的BuffaloCall; 5、 通过分析BuffaloCall对应业务服务各方法参数的匹配权重,获取最合适的调用方法,并调用,匹配过的方法,将置入缓存以提升性能; 6、 根据调用结果,需要通过BuffaloProtocal实例marshall结果,并通过StreamWriter(wrap了response.getOutputStream())写回调用客户端; 7、 客户端通过Buffalo.Reply解析返回的结果,并实现与UI的交互。
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
3. Buffalo的协议分析3.1. 概述协议就是前后端用以通信的约定,Buffalo提供的是XML的协议。 请求过程是协议unmarshal的过程,需要通过解析request.getInputStream();而把业务执行结果写回客户端的过程是协议的marshal过程。(说句题外话,Michael应该是笔误了,在BuffaloProtocal类中写成了unmarshall/marshall) 显然Michael是主张测试驱动的开发,单元测试用例写的非常到位,应该说覆盖性是没问题。通过测试用例学习协议,是不错的办法。 当然了,也可直接参考官方网站的说明:http://buffalo.sourceforge.net/protocol.html
所有的请求,都通过如下格式发出: buffalo.remoteCall({Service.Method}, {Params}, {CallBackFunction}) 其中: Service.Method——Service为在buffalo-service.properties或Spring注册的服务标识; Params——参数Array,没有参数则定义一个空的数组“[]”(建议对null进行保护); CallBackFunction——回调函数,提供一个Buffalo.Reply参数 最终给服务器端发出的请求,是标准的基于UTF-8的XML,格式举例如下:
例子 说明 <buffalo-call> <method>sum</method> <double>1</double> <double>2</double> </buffalo-call> ->这是Service对应的方法 ->之后的都是参数,这里表示有两个参数,均为double |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
3.4. fault异常对于异常,我们经常需要关注三个东西:异常编码、异常信息和详细堆栈。 Buffalo关注业务服务所抛出的异常。 Buffalo通过捕捉java.lang.reflect.InvocationTargetException异常,使用ExceptionConverter转换器提供了类似于Map的三个属性,code, message, detail。 其中: code——异常名称,即ex.getClass().getName() message——异常消息,即ex.getMessage() detail——异常详细消息,如果有,则为ex.getCause().getMessage()
相关代码如下:
3.5. 其他Buffalo还实现了一些例外的对象的协议定义和转换。 3.5.1. java.sql.Date
3.5.2. java.math.BigDecimal/ java.math.BigInteger
You can use object.value to get the real value of those objects. When deserializing, it will use the constructor BigDecimal(String) or BigInteger(String) to create a new one. 3.5.3. 还不够?那就自己写,实现Converter,但需要修改(目前只能这么干,BuffaloProtocal这个类应该要完善下,可方便实现配置/注入)如下注册类: net.buffalo.protocal.converters.DefaultConverterLookup.java
Converter包括三个接口:
提示:可以参考xstream进行扩展,但一般的WEB开发,这些转换器还是够了的。 |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
4. 核心类分析4.1. 概述先来看看代码结构,如下:
整体而言,与xstream有点像,但考虑到Buffalo是贯穿前后端的Ajax框架,还包括了web/view/request等部分。 代码的核心部分,主要包括了两个部分,分别为service和protocol,下面分别重点分析说明。
此为Buffalo目前实现了的BuffaloWorker为核心的package,主要包括了业务服务Repository、业务服务方法适配定位和业务服务调用等部分。 核心类图如下: |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
4.3. protocol协议在第三章节的“Buffalo的协议分析”中做了详细的描述,这章主要分析代码。 协议部分的代码,总体分为converters(转换器)、io(协议marshal/unmarshal)两个部分,。 从代码的结构和设计技巧可以看出,Michael深入研究过xstream的代码。 4.3.1. converters(类型转换解析器)转换器的代码实现了我们WEB开发常用的类型解析,从代码结构分为basic、collection、map,还有一些exceptional(例外)的类型解析。 Converter的接口定义为:
类图罗列如下:
总而言之,这些代码质量都非常高,设计精巧,合理有效使用了各种设计模式,尤其是Template模式运用的非常精彩。 需要说明的是,转换器可对Array及实现了collection接口的对象进行处理,也可对Map结果的对象进行了完美的支持,而POJO其实在unmarshal与Map是一样的而仅仅在marshal上有些特殊。 4.3.2. io(协议marshal/unmarshal)io这块代码是最复杂的代码之一,涉及到协议解析的很多细节。代码由于解释过少,有些代码我需要反复调试多次才能明白具体意义——可能跟个人能力和资质有关,汗一个。 类图说明如下:
包括五个非常重要的接口,即MarshallingContext、UnmarshallingContext、MarshallingStrategy、StreamReader和StreamWriter。分别如下: MarshallingContext:
UnmarshallingContext:
MarshallingStrategy:
StreamReader:
StreamWriter:
从客户端读入的流(request.getInputStream()),将使用实现了MarshallingStrategy接口的DefaultMarshallingStrategy的unmarshal方法,并通过实现了StreamReader的FastStreamReader解析输入流(为XML),获得必要的目标业务服务的方法和参数,以最终构建BuffaloCall对象,并最终得以调用(可参见“服务的匹配与调用”章节)。 业务服务调用完毕后,将使用实现了MarshallingStrategy接口的DefaultMarshallingStrategy的marshal方法,并通过实现了StreamWriter的FastStreamWriter输入协议流(为XML)到客户端,实现了XML协议的解析与交换。 FastStreamReader那种一个字符一个字符读进来并解析的过程,我是在鼓足勇气后才通读完毕的,并通过多次调试才理解所有的细节,过程中还补了不少课,善哉善哉。(以后如果要通读xstream,只怕这种勇气得更大更多,^0*) 解读过程得益于Buffalo提供的单元测试类,再次感受到TDD的好处咯。
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
最后修改:2010-01-11
5. 协议可能需要完善的专题讨论5.1. 深度分析首先声明:在未与Michael充分沟通交流前,一切都只是个人的想法,未必正确。 本人在“服务的匹配与调用”章节,曾经提出过服务方法匹配的问题,Michael在buffalo.js的310行提到过“Guess the type of an javascript object”,但光完善客户端,只怕于事无补。 就如我做的实验那样,客户端可以提供divide(double a, double b)的请求,此时的xml为: <buffalo-call> <method>divide</method> <double>1.1</double> <double>2.1</double> </buffalo-call>
此时调用的是divide(Double a, Double b)而不是divide(double a, double b)。 虽然已经相差无几,但依然无法到达准确制导,主要还是primitive及其Wrapper之间容易造成误会。 首先我们还是仔细来看看匹配权重的代码,在相同服务下,同名称同参数长度下,下一步就考虑到了参数的匹配权重问题(这里我加了注释):
题外话:注意下后面的if/else代码稍微有些逻辑交待不清。 同时,需要结合所涉及到的Converter,此时为DoubleConverter。同样我们看看代码:
public class DoubleConverter extends AbstractBasicConverter implements Converter{ public boolean canConvert(Class value) { if (value == null) return false; return value.equals(double.class) || value.equals(Double.class) || value.equals(float.class) || value.equals(Float.class); } protected String getType() { return ProtocalTag.TAG_DOUBLE; } public Object fromString(String string) { return Double.valueOf(string); } }
显然,在这个例子上,是这个转换器模糊了double和Double,统统变成了Double。 5.2. 可能的解决方案当然,这里的解决方案是以“需要这么干”为前提假设的。在很大程度上来说,这种“精确制导”没有必要。 我仅以解决上述例子为目标,做出修缮。 5.2.1. 在ProtocalTag中加入新协议标签位置:net.buffalo.protocal.ProtocalTag 需要加入新的标签定义:
Primitive对象的包装类:
一个获取primitive类型的工具:
位置:net.buffalo.protocal.converters.primitive 新增primitive的package,并建立新的转换器PrimitiveDoubleConverter.java: 位置:net.buffalo.protocal.converters.basic.DoubleConverter.java 不再是ProtocalTag.TAG_DOUBLE了: 位置:net.buffalo.protocal.converters.DefaultConverterLookup.java
位置:net.buffalo.protocal.io.FastStreamReader 位置:net.buffalo.protocal.BuffaloCall
public BuffaloCall(String methodName, Object[] arguments) { this.argumentTypes = new Class[arguments.length]; this.arguments = new Object[arguments.length]; for (int i = 0; i < arguments.length; i++) { // from code //types[i] = arguments[i].getClass(); // to - begin if(arguments[i] instanceof PrimitiveObjectWrapper){ PrimitiveObjectWrapper p = (PrimitiveObjectWrapper)arguments[i]; //the type this.argumentTypes[i] = PrimitiveObjectUtil.getPrivitiveType(p.getValue()); //the value this.arguments[i] = p.getValue(); }else{ this.argumentTypes[i] = arguments[i].getClass(); this.arguments[i] = arguments[i]; } //end } this.methodName = methodName; }
5.2.8. 客户端buffalo.js需要处理客户端对于java.lang.Double标签的处理,在Buffalo.Reply中(注意红色的部分):
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
5.3. 测试结果输入:
提交:
结果: 1)日志:
2)界面:
OK,正是我们想要的。 当然了,还有两点需要说明: 1)其他的诸如Float等Primitive类型的处理,照葫芦画瓢即可; 2)客户端buffalo.js还应该有少量的完善工作,重点在于类型的处理上,应该考虑的更周全些。 6. 参考一、Buffalo官方网站: http://buffalo.sourceforge.net
二、buffalo.jar 包分析: http://blog.csdn.net/jianglike18/archive/2009/04/11/4062630.aspx http://blog.csdn.net/jianglike18/archive/2009/05/05/4151558.aspx |
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
我们的应用中用到了buffalo,不错,国产的ajax框架,只是2.0之后就不再更新了
|
|||||||||||||||
返回顶楼 | |||||||||||||||
发表时间:2010-01-11
第一次了解Buffalo,
跟我的rpcfx很像,不过我的还没有成型, 最近一次大的改动,已经跑不起来了 :) http://code.google.com/p/rpcfx/ 旧的版本 实现了java .net服务器、客户端的互相调用 都有dwr的影子。 |
|||||||||||||||
返回顶楼 | |||||||||||||||