Buffalo深度研究
——2010.01.11, IT进行时[MSN:zhengxianquan AT hotmail Dot com]
Buffalo是一个前后贯通的完整的Ajax框架,目前最新的版本是2.0.1,其主页是:
http://buffalo.sourceforge.net/,可通过该页面找到下载。
不过该版本自2007年来就没有更新了,有点遗憾,不管怎样,一出来就关注到了,早前通读过代码,是个好作品。
上周开始用了些零碎的时间,重新评估并进行了深入的研究,其目的在于通过深度掌握某个优秀的贯穿前后端的AJAX框架,以提高自己的整体认知感。
抄自:http://buffalo.sourceforge.net/features.html
一、JavaScript implementation of a lightweight xml protocal
Buffalo is using a lightweight protocal(a subset of burlap with minor modification) and it is very good for web remoting, simple yet enough. Buffalo implementation is including serializing and deserializing for javascript objects.
二、Full support for java to javascript serializing/deserializing
Any method invokation result at java side, will be serialize to javascript side transparently, no matter how complicated of this object is. Buffalo can serialize from primitive type (String, int, long, boolean, etc) and Object type(List, Map even your own business domain object). You can access the same property at the javascript side. his sophiscate feature has been proven in various real projects.
The client javascript:
buffalo.remoteCall("userService.listAll",[],function(reply){
var userList = reply.getResult();
var firstUserFamilyName = userList[0].name.familyName;
});
|
三、Callback based programming model
Every remote call could be like below:
var buffalo = new Buffalo("/bfapp/buffalo");
buffalo.remoteCall("userService.login",["username","password"], function(reply) {
var success = reply.getResult();
if (success) {
alert("You login successfully");
} else {
alert("user name or password incorrect!");
}
});
|
This kind of API is very easy to learn and use. Every user could use it in half an hour.
四、Support asynchonize events
Love the gmail-like loading? buffalo give you! When calling the remote method, a loading panel is shown on the top-right corner of the browser. You can also customize onLoad, onFinish, onError events to display your own infomation.
五、Straightforward, easy to use API
Buffalo developers try their best to make the API easy to use on both server side and client side. The buffalo users do not need to know the detail implementation.
Server side. Every POJO can be exposed as a buffalo service. No need to write buffalo specified java file.
Client side. What the users need is only one Buffalo Object with a couple of methods. It is so easy that every user could get used of it in less than half an hour.
六、Integrated with prototype javascript library
Buffalo client scripts build on top of the famous prototype library, using its class facility and element selector. You can get the benefit directly from prototype provided convenient infrastructure.
七、Spring integration
Spring is the most popular IoC container. Every bean managed by spring can be used as buffalo service with simple configuration.
八、Browser Compatibility
All features support IE/Firefox, remoting features support IE5.5+/Firefox1.0+/Safari/Opera9+.
九、Browser back/forward support
Most the AJAX applications does not support browser back/forward, such as MSN Live Mail. Buffalo solve this problem. What you need to do is add a reference of buffalo-blank.html as iframe, and use buffalo.switchView to navigate your page, you will find the navigation will work well on your browser. (Tested on IE/Firefox)
十、Support data binding
For most common used elements in HTML, buffalo provide the binding feature, which can bind the javascript object to the element. Now we support text, checkbox, radio, textarea, select, span/div, table.
Buffalo最有价值之初,个人感觉有两点:
1、 后端实现了较为完整的基于xml的xml<->object序列化反序列化协议;
2、 前端提供了适配协议的调用封装和响应解析机制,并基于回调机制提供编码API。
另,作品受xstream影响颇深,如果看过xstream的代码大家的感觉会更明显,不知道这样说Michael是否有意见:buffalo后端转换器、IO部分的代码,是xstream的lightweight版本:)
要了解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的交互。
协议就是前后端用以通信的约定,Buffalo提供的是XML的协议。
请求过程是协议unmarshal的过程,需要通过解析request.getInputStream();而把业务执行结果写回客户端的过程是协议的marshal过程。(说句题外话,Michael应该是笔误了,在BuffaloProtocal类中写成了unmarshall/marshall)
显然Michael是主张测试驱动的开发,单元测试用例写的非常到位,应该说覆盖性是没问题。通过测试用例学习协议,是不错的办法。
当然了,也可直接参考官方网站的说明:http://buffalo.sourceforge.net/protocol.html
协议支持情况一览表如下:
Tag
|
Description
|
Mapped Java Class
|
Mapped Javascript Class
|
boolean
|
the boolean value, 1 means true and 0 means false
|
java.lang.Boolean , boolean.class
|
boolean
|
date
|
date, ISO8609 format, for example 20061011T230201Z means Oct 11 2006, 23:02:01
|
java.util.Date
|
Date
|
int
|
int value
|
java.lang.Integer, java.lang.Short, java.lang.Byte and their primitive types
|
int
|
long
|
long value
|
java.lang.Long and its primitive type
|
int
|
null
|
null value
|
null
|
null or undefined
|
string
|
string
|
java.lang.String, java.lang.Character, char.class
|
String
|
type
|
indicate the type of the list or map
|
N/A
|
N/A
|
length
|
indicate the length of the list
|
N/A
|
N/A
|
list
|
list or array data structure
|
java.util.Collection capatible or Array
|
Array
|
map
|
map or object data structure
|
java.util.Map assignable or java bean
|
object
|
double
|
double
|
java.lang.Double, java.lang.Float and their primitive types
|
float
|
ref
|
reference of an object
|
N/A
|
N/A
|
fault
|
exception
|
will NOT convert from client side as throw an exception from client side make no sense
|
Buffalo.Fault
|
buffalo-call
|
the root element of client remote call
|
N/A
|
N/A
|
method
|
the method client want to call
|
N/A
|
N/A
|
buffalo-reply
|
the root element of the server reply
|
N/A
|
N/A
|
所有的请求,都通过如下格式发出:
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
|
返回比较复杂,不同的情况需要区别对待。
Array or java.util.Collection assignable value will be convert to list.
例子
|
说明
|
<list>
<type>java.util.ArrayList</type>
<length>2</length>
<string>String#1</string>
<string>String#2</string>
</list>
|
//如果后端需要返回list的话
List list = new ArrayList();
list.add("String#1");
list.add("String#2");
|
例子
|
说明
|
<list>
<type>[java.lang.String</type>
<length>2</length>
<string>String#1</string>
<string>String#2</string>
</list>
|
//如果后端需要返回strings的话
String[] strings = new String[]{"String#1", "String#2"}
|
Map indicate a map-liked data structure.
The java.util.Map assignable value or POJO will use this.
一、真正的MAP:
例子
|
说明
|
<map>
<type>java.util.HashMap</type>
<string>key1</string>
<string>value1</string>
<int>1</int>
<double>2.0</double>
</map>
|
//如果后端需要返回map的话
Map map = new HashMap()
map.put("key1", "value1");
map.put(new Integer(1), new Double(2.0));
|
二、POJO也这样:
例子
|
说明
|
<map>
<type>domain.user</type>
<string>name</string>
<string>John Smith</string>
<string>age</string>
<int>age</int>
<string>gendor</string>
<boolean>1</boolean>
</map>
|
//如果后端需要返回user的话
package domain;
class User {
String name;
int age;
boolean gendor;
//getters & setters...
}
User user = new User("John Smith", 30, true);
|
Ref means a reference to another object. This tag is really useful dealling with the circular reference, otherwise a StackOverflowException could be easily thrown.
例子
|
说明
|
<list>
<type>java.util.ArrayList</type>
<length>2</length>
<map>
<type>net.buffalo.protocal.People</type>
<string>name</string>
<string>John</string>
……
<string>friend</string>
<map>
<type>net.buffalo.protocal.People</type>
<string>name</string>
<string>Michael</string>
……
<string>friend</string>
<ref>1</ref>
</map>
</map>
<ref>2</ref>
</list>
|
//如果后端需要返回user的话
class People...
People getFriend() ...
// Other fields ommited
People john = new People("John");
People michael = new People("Michael");
john.setFriend(michael);
michael.setFriend(josh);
List friends = new ArrayList();
friends.add(john);
friends.add(michael);
|
其实在marshal的过程中,所有对象都会置入一个ArrayList中,作为对象引用列表,每次都会判断当前的value是否在引用列表中,如果有则使用Ref的ProtocalTag,其值为当前value在引用列表的index。具体请参考接口MarshallingContext。
注意,我把上面的例子修改了下,会变成如下结果:
例子
|
说明
|
<list>
<type>java.util.ArrayList</type>
<length>2</length>
<map>
<type>net.buffalo.protocal.People</type>
<string>name</string>
<string>John</string>
……
<string>friend</string>
<map>
<type>net.buffalo.protocal.People</type>
<string>name</string>
<string>Michael</string>
……
<string>friend</string>
<null></null>
</map>
</map>
<ref>2</ref>
</list>
|
//如果后端需要返回user的话
class People...
People getFriend() ...
// Other fields ommited
People john = new People("John");
People michael = new People("Michael");
john.setFriend(michael);
michael.setFriend(josh);
List friends = new ArrayList();
friends.add(john);
//friends.add(michael);
|
此时该属性序列化为null。
对于异常,我们经常需要关注三个东西:异常编码、异常信息和详细堆栈。
Buffalo关注业务服务所抛出的异常。
Buffalo通过捕捉java.lang.reflect.InvocationTargetException异常,使用ExceptionConverter转换器提供了类似于Map的三个属性,code, message, detail。
其中:
code——异常名称,即ex.getClass().getName()
message——异常消息,即ex.getMessage()
detail——异常详细消息,如果有,则为ex.getCause().getMessage()
相关代码如下:
public void marshal(Object source, MarshallingContext marshallingContext, StreamWriter streamWriter) {
Throwable ex = (Throwable) source;
String detail = "";
if (ex.getCause() != null) {
detail = "caused by: " + ex.getCause().getMessage();
}
streamWriter.startNode(ProtocalTag.TAG_FAULT);
node(streamWriter, ProtocalTag.TAG_STRING, "code");
node(streamWriter, ProtocalTag.TAG_STRING, ex.getClass().getName());
node(streamWriter, ProtocalTag.TAG_STRING, "message");
node(streamWriter, ProtocalTag.TAG_STRING, ex.getMessage());
node(streamWriter, ProtocalTag.TAG_STRING, "detail");
node(streamWriter, ProtocalTag.TAG_STRING, detail);
streamWriter.endNode();
}
|
Buffalo还实现了一些例外的对象的协议定义和转换。
例子
|
说明
|
<map>
<type>java.sql.Date</type>
<string>value</string>
<date>20061018T211400Z</date>
</map>
|
注意多了个value,且值是经过特定格式化的
|
例子
|
说明
|
<map>
<type>java.math.BigDecimal</type>
<string>value</string>
<string>1234567890</string>
</map>
|
注意多了个value
|
例子
|
说明
|
<map>
<type> java.math.BigInteger</type>
<string>value</string>
<string>1234567890</string>
</map>
|
注意多了个value
|
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.
那就自己写,实现Converter,但需要修改(目前只能这么干,BuffaloProtocal这个类应该要完善下,可方便实现配置/注入)如下注册类:
net.buffalo.protocal.converters.DefaultConverterLookup.java
Converter包括三个接口:
public interface Converter {
boolean canConvert(Class type);
void marshal(Object source, MarshallingContext marshallingContext, StreamWriter streamWriter);
Object unmarshal(StreamReader reader, UnmarshallingContext unmarshallingContext);
}
|
提示:可以参考xstream进行扩展,但一般的WEB开发,这些转换器还是够了的。
先来看看代码结构,如下:
整体而言,与xstream有点像,但考虑到Buffalo是贯穿前后端的Ajax框架,还包括了web/view/request等部分。
代码的核心部分,主要包括了两个部分,分别为service和protocol,下面分别重点分析说明。
此为Buffalo目前实现了的BuffaloWorker为核心的package,主要包括了业务服务Repository、业务服务方法适配定位和业务服务调用等部分。
核心类图如下:
服务的Repository定义了统一的ServiceRepository接口:
public interface ServiceRepository {
/**
* The default service properties file location
*/
public static final String DEFAULT_SERVICES = "/buffalo-service.properties";
/**
* The key for servlet context
*/
public static final String WEB_CONTEXT_KEY = ServiceRepository.class + "_WEB_KEY";
/**
* Register a service to repository
*
* @param serviceId the service key
* @param serviceName the service name
* @param factoryId the factory id
*/
public void register(String serviceId, String serviceName , String factoryId);
/**
* Get the service instance from repository
* @param serviceId the service key
* @return service instance
*/
public Object get(String serviceId);
}
|
目前Buffalo提供了默认的实现,为net.buffalo.service.DefaultServiceRepository,该Repository在请求ApplicationServlet的init方法中初始化,意味着在第一次Buffalo请求时完成服务的注册,并对外提供统一的get服务。
另外,提供了统一的ServiceFactory接口,用以实现服务的创建。
package net.buffalo.service;
/**
* Make a service factory to handle the service creation.
*
* @author michael
* @version 1.2
* @since 1.2alpha2
*/
public interface ServiceFactory {
/**
* Default implementation
*/
public static final String DEFAULT = "default";
/**
* Spring implementation
*/
public static final String SPRING = "spring";
/**
* return a service instance based on the serviceId and the service name.
*
* @param serviceId the service key, such a loginService, dataService...
* @param serviceName the name, may be className by the default implementation,
* and the spring config bean name by the spring implementation
* @return service instance
* @throws NoSuchServiceException if serivice id not found
* @throws ServiceCreationFailException if service creation failed
*/
public Object getService(String serviceId, String serviceName)
throws NoSuchServiceException, ServiceCreationFailException;
}
|
并提供了两种实现:
1、 默认基于/buffalo-service.properties配置的实现,为DefaultServiceFactory;
2、 以及基于spring的SpringServiceFactory。
此时,DefaultServiceRepository将优先自动加载基于属性文件的服务生成(用Class.forName(serviceClass).newInstance()来创建),并通过判断spring是否可用,以确定是否能根据配置要求注册服务。
基于buffalo-service.properties的配置举例,如下:
# simpleService, The simple Service
simpleService=net.buffalo.demo.simple.SimpleService
# The number guess service
numberService=net.buffalo.demo.numberguess.NumberGuessService
|
基于Spring的注册,则通过注册了指定类(BuffaloServiceConfigurer.class)的特定属性(services),以获得服务清单,代码如下:
String[] buffaloConfigBeanNames =
appCtx.getBeanNamesForType(BuffaloServiceConfigurer.class);
|
配置举例如下,此时将取得service1和service2两个注册服务并放入Repository:
<beans>
<bean name="buffaloConfigBean" class="net.buffalo.service.BuffaloServiceConfigurer">
<property name="services">
<map>
<entry key="service1">
<ref bean="service1"/>
</entry>
<entry key="service2">
<ref bean="service2"/>
</entry>
</map>
</property>
</bean>
<bean name="service1" class="example.service1" />
<bean name="service2" class="example.service2" />
</beans>
|
ServiceInvoker是服务调用的借款,只有一个方法,即invode。
BuffaloInvoker是其默认实现,通过pathInfo规则确定服务(注册)名称,并通过lookupMethod,确定最佳调用方法,即可完成最终业务服务的invoke。
lookupMethod过程考虑了一个matchWeight(匹配权重)问题,其目标是要找到最佳的调用方法。需要注意的是,这个权重的规则并不一定好用。我做了实验,在默认提供的DEMO的SimpleService中,增加了几个方法,如下:
public double divide(double a, double b) {
logger.info("Calling Divide in divide(double a, double b),a="+a+", b="+b);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return a/b;
}
public double divide(Double a, double b) {
logger.info("Calling Divide in divide(Double a, double b),a="+a+", b="+b);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return a/b;
}
public double divide(Double a, Double b) {
logger.info("Calling Divide in divide(Double a, Double b),a="+a+", b="+b);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return a/b;
}
public double divide(int a, int b) {
logger.info("Calling Divide in divide(int a, int b),a="+a+", b="+b);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return a/b;
}
|
此时如果界面中输入1和2并提交,则返回为0。如下:
日志如下:
2010-1-9 22:23:05 net.buffalo.demo.simple.SimpleService divide
信息: Calling Divide in divide(int a, int b),a=1, b=2
|
表示调用的是divide(int a, int b),自然返回的是0了,这可能不是我们想要的。
而如果我输入的是这样的:
此时日志如下:
2010-1-9 22:25:50 net.buffalo.demo.simple.SimpleService divide
信息: Calling Divide in divide(Double a, Double b),a=1.1, b=2.0
|
此时调用的是divide(Double a, Double b),可能差不多了,无论怎样,总是调用不到divide(double a, double b)。
为了避免出现麻烦,最好避免同一个服务中,同名且同数量参数的方法的出现。(其实也还好了,相对于动态产生js存根会导致同名方法无法有效调用——只能调用最后一个同名方法的DWR,感觉已经进了一大步咯)
此时有两个问题需要思考:
1、 是否有必要优化?
2、 可否进一步优化?
我个人的感觉,一般的WEB应用应该不会要求服务定位精度这么高,大可不必去优化;而如果要优化的话,则会比较复杂,需要修改、完善Buffalo所用协议的不少内容,这点将在下一章节专题讨论。
为了性能考虑,匹配过的方法,都会置入ServiceInvoker的缓存
协议在第三章节的“Buffalo的协议分析”中做了详细的描述,这章主要分析代码。
协议部分的代码,总体分为converters(转换器)、io(协议marshal/unmarshal)两个部分,。
从代码的结构和设计技巧可以看出,Michael深入研究过xstream的代码。
转换器的代码实现了我们WEB开发常用的类型解析,从代码结构分为basic、collection、map,还有一些exceptional(例外)的类型解析。
Converter的接口定义为:
public interface Converter {
boolean canConvert(Class type);
void marshal(Object source, MarshallingContext marshallingContext, StreamWriter streamWriter);
Object unmarshal(StreamReader reader, UnmarshallingContext unmarshallingContext);
}
|
类图罗列如下:
总而言之,这些代码质量都非常高,设计精巧,合理有效使用了各种设计模式,尤其是Template模式运用的非常精彩。
需要说明的是,转换器可对Array及实现了collection接口的对象进行处理,也可对Map结果的对象进行了完美的支持,而POJO其实在unmarshal与Map是一样的而仅仅在marshal上有些特殊。
4.3.2. io(协议marshal/unmarshal)
io这块代码是最复杂的代码之一,涉及到协议解析的很多细节。代码由于解释过少,有些代码我需要反复调试多次才能明白具体意义——可能跟个人能力和资质有关,汗一个。
类图说明如下:
包括五个非常重要的接口,即MarshallingContext、UnmarshallingContext、MarshallingStrategy、StreamReader和StreamWriter。分别如下:
MarshallingContext:
public interface MarshallingContext {
void convertAnother(Object value);
List getObjects();
void addObjectRef(Object object);
}
|
UnmarshallingContext:
public interface UnmarshallingContext {
Object convertAnother();
void addObject(Object object);
List getObjects();
}
|
MarshallingStrategy:
public interface MarshallingStrategy {
void marshal(Object obj, ConverterLookup converterLookup, StreamWriter streamWriter);
BuffaloCall unmarshal(StreamReader reader, ConverterLookup converterLookup);
}
|
StreamReader:
public interface StreamReader {
boolean hasMoreChildren();
void moveDown();
void moveUp();
String getNodeName();
String getValue();
void close();
}
|
StreamWriter:
public interface StreamWriter {
void startNode(String name);
void setValue(String text);
void endNode();
void flush();
void close();
}
|
从客户端读入的流(request.getInputStream()),将使用实现了MarshallingStrategy接口的DefaultMarshallingStrategy的unmarshal方法,并通过实现了StreamReader的FastStreamReader解析输入流(为XML),获得必要的目标业务服务的方法和参数,以最终构建BuffaloCall对象,并最终得以调用(可参见“服务的匹配与调用”章节)。
业务服务调用完毕后,将使用实现了MarshallingStrategy接口的DefaultMarshallingStrategy的marshal方法,并通过实现了StreamWriter的FastStreamWriter输入协议流(为XML)到客户端,实现了XML协议的解析与交换。
FastStreamReader那种一个字符一个字符读进来并解析的过程,我是在鼓足勇气后才通读完毕的,并通过多次调试才理解所有的细节,过程中还补了不少课,善哉善哉。(以后如果要通读xstream,只怕这种勇气得更大更多,^0*)
解读过程得益于Buffalo提供的单元测试类,再次感受到TDD的好处咯。
首先声明:在未与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之间容易造成误会。
首先我们还是仔细来看看匹配权重的代码,在相同服务下,同名称同参数长度下,下一步就考虑到了参数的匹配权重问题(这里我加了注释):
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就是没啥匹配度了
}
|
题外话:注意下后面的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。
当然,这里的解决方案是以“需要这么干”为前提假设的。在很大程度上来说,这种“精确制导”没有必要。
我仅以解决上述例子为目标,做出修缮。
位置:net.buffalo.protocal.ProtocalTag
需要加入新的标签定义:
package net.buffalo.protocal;
public interface ProtocalTag {
public static final String TAG_BOOLEAN = "boolean";
public static final String TAG_DATE = "date";
public static final String TAG_INT = "int";
public static final String TAG_LONG = "long";
public static final String TAG_NULL = "null";
public static final String TAG_STRING = "string";
public static final String TAG_LENGTH = "length";
public static final String TAG_TYPE = "type";
public static final String TAG_LIST = "list";
public static final String TAG_MAP = "map";
public static final String TAG_DOUBLE = "double";
public static final String TAG_REPLY = "buffalo-reply";
public static final String TAG_REF = "ref";
public static final String TAG_CALL = "buffalo-call";
public static final String TAG_FAULT = "fault";
public static final String TAG_METHOD = "method";
//加入新的协议标签 by zhengxq
//java.lang.Double
public static final String TAG_DOUBLE_W = "java.lang.Double";
//其他}
|
Primitive对象的包装类:
package net.buffalo.protocal.util;
public class PrimitiveObjectWrapper {
private Class clazz;
private Object value;
public PrimitiveObjectWrapper(Object value){
this.clazz = PrimitiveObjectUtil.getPrivitiveType(value);
this.value = value;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
|
一个获取primitive类型的工具:
package net.buffalo.protocal.util;
public class PrimitiveObjectUtil {
public static Class getPrivitiveType(Object v){
Class clazz = v instanceof Integer ? int.class :
v instanceof Long ? long.class :
v instanceof Short ? short.class :
v instanceof Byte ? byte.class :
v instanceof Float ? float.class :
v instanceof Double ? double.class :
v instanceof Boolean ? boolean.class :
v.getClass();
return clazz;
}
}
|
位置:net.buffalo.protocal.converters.primitive
新增primitive的package,并建立新的转换器PrimitiveDoubleConverter.java:
package net.buffalo.protocal.converters.primitive;
import net.buffalo.protocal.ProtocalTag;
import net.buffalo.protocal.converters.Converter;
import net.buffalo.protocal.converters.basic.AbstractBasicConverter;
import net.buffalo.protocal.util.PrimitiveObjectWrapper;
public class PrimitiveDoubleConverter extends AbstractBasicConverter implements Converter{
public boolean canConvert(Class value) {
if (value == null) return false;
return value.equals(double.class);
}
protected String getType() {
return ProtocalTag.TAG_DOUBLE;
}
public Object fromString(String string) {
//get the custom Wrapper
return new PrimitiveObjectWrapper(Double.valueOf(string));
}
}
|
位置:net.buffalo.protocal.converters.basic.DoubleConverter.java
不再是ProtocalTag.TAG_DOUBLE了:
protected String getType() {
return ProtocalTag.TAG_DOUBLE_W;
}
|
位置:net.buffalo.protocal.converters.DefaultConverterLookup.java
protected void registerDefaultConverters() {
BooleanConverter booleanConverter = new BooleanConverter();
DoubleConverter doubleConverter = new DoubleConverter();
IntegerConverter integerConverter = new IntegerConverter();
LongConverter longConverter = new LongConverter();
StringConverter stringConverter = new StringConverter();
DateConverter dateConverter = new DateConverter();
CollectionConverter collectionConverter = new CollectionConverter();
MapConverter mapConverter = new MapConverter();
ArrayConverter arrayConverter = new ArrayConverter();
SqlDateConverter sqlDateConverter = new SqlDateConverter();
BigNumberConverter bigNumberConverter = new BigNumberConverter();
ExceptionConverter exceptionConverter = new ExceptionConverter();
ObjectConverter objectConverter = new ObjectConverter();
//新增新的转换器定义
PrimitiveDoubleConverter primitiveDoubleConverter = new PrimitiveDoubleConverter();
converters.add(nullConverter);
converters.add(booleanConverter);
converters.add(doubleConverter);
converters.add(integerConverter);
converters.add(longConverter);
converters.add(stringConverter);
converters.add(dateConverter);
converters.add(collectionConverter);
converters.add(mapConverter);
converters.add(arrayConverter);
converters.add(sqlDateConverter);
converters.add(bigNumberConverter);
converters.add(exceptionConverter);
// register new primitiveDoubleConverter
converters.add(primitiveDoubleConverter);
// Should be last one
converters.add(objectConverter);
tagNameConverterCache.put(ProtocalTag.TAG_BOOLEAN, booleanConverter);
tagNameConverterCache.put(ProtocalTag.TAG_STRING, stringConverter);
tagNameConverterCache.put(ProtocalTag.TAG_INT, integerConverter);
tagNameConverterCache.put(ProtocalTag.TAG_LONG, longConverter);
tagNameConverterCache.put(ProtocalTag.TAG_NULL, nullConverter);
tagNameConverterCache.put(ProtocalTag.TAG_DATE, dateConverter);
tagNameConverterCache.put(ProtocalTag.TAG_LIST, collectionConverter);
tagNameConverterCache.put(ProtocalTag.TAG_MAP, mapConverter);
tagNameConverterCache.put(ProtocalTag.TAG_REF, new ReferenceConverter());
// edit the doubleConverter
tagNameConverterCache.put(ProtocalTag.TAG_DOUBLE_W, doubleConverter);
// register new primitiveDoubleConverter
tagNameConverterCache.put(ProtocalTag.TAG_DOUBLE, primitiveDoubleConverter);
converterCache.put(Boolean.class, booleanConverter);
converterCache.put(boolean.class, booleanConverter);
converterCache.put(String.class, stringConverter);
converterCache.put(Integer.class, integerConverter);
converterCache.put(int.class, integerConverter);
converterCache.put(Long.class, longConverter);
converterCache.put(long.class, longConverter);
converterCache.put(Double.class, doubleConverter);
// edit the double converter mapping
//converterCache.put(double.class, doubleConverter);
converterCache.put(double.class, primitiveDoubleConverter);
converterCache.put(Date.class, dateConverter);
converterCache.put(ArrayList.class, collectionConverter);
converterCache.put(LinkedList.class, collectionConverter);
converterCache.put(HashSet.class, collectionConverter);
converterCache.put(Vector.class, collectionConverter);
converterCache.put(TreeSet.class, collectionConverter);
converterCache.put(HashMap.class, mapConverter);
converterCache.put(TreeMap.class, mapConverter);
converterCache.put(java.sql.Date.class, sqlDateConverter);
converterCache.put(java.math.BigDecimal.class, bigNumberConverter);
converterCache.put(java.math.BigInteger.class, bigNumberConverter);
}
|
位置:net.buffalo.protocal.io.FastStreamReader
/* 判断是否有效的标签字符,必须是字母、数字和两个特定字符:':'、'-'
* 又新增了字符'.'
*/
private boolean isTagChar(int ch) {
return (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0'
&& ch <= '9' || ch == ':' || ch == '-' || ch == '.');
}
|
位置: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;
}
|
需要处理客户端对于java.lang.Double标签的处理,在Buffalo.Reply中(注意红色的部分):
deserialize: function(dataNode) {
var ret;
var type = this._getType(dataNode);
switch (type) {
case "boolean": ret = this.doBoolean(dataNode); break;
case "date": ret = this.doDate(dataNode); break;
case "java.lang.double":;//new add
case "double": ret = this.doDouble(dataNode); break;
case "int":
case "long":
ret = this.doInt(dataNode);
break;
case "list": ret = this.doList(dataNode); break;
case "map": ret = this.doMap(dataNode); break;
case "null": ret = this.doNull(dataNode); break;
case "ref": ret = this.doRef(dataNode); break;
case "string": ret = this.doString(dataNode);break;
case "xml": ret = this.doXML(dataNode); break;
case "fault": ret = this.doFault(dataNode); break;
default: ;
}
return ret;
}
|
输入:
提交:
<buffalo-call>
<method>divide</method>
<double>1.1</double>
<double>2.2</double>
</buffalo-call>
|
结果:
1)日志:
2010-1-11 13:25:40 net.buffalo.demo.simple.SimpleService divide
信息: Calling Divide in divide(double a, double b),a=1.1, b=2.2
|
2)界面:
OK,正是我们想要的。
当然了,还有两点需要说明:
1)其他的诸如Float等Primitive类型的处理,照葫芦画瓢即可;
2)客户端buffalo.js还应该有少量的完善工作,重点在于类型的处理上,应该考虑的更周全些。
一、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