Ajax框架Buffalo深度研究

Buffalo深度研究

——2010.01.11, IT进行时[MSN:zhengxianquan AT hotmail Dot com]

     同步在javaeye上发布:http://www.javaeye.com/topic/567488

1.  
Buffalo
概述

Buffalo是一个前后贯通的完整的Ajax框架,目前最新的版本是2.0.1,其主页是:

http://buffalo.sourceforge.net/,可通过该页面找到下载。

不过该版本自2007年来就没有更新了,有点遗憾,不管怎样,一出来就关注到了,早前通读过代码,是个好作品。

上周开始用了些零碎的时间,重新评估并进行了深入的研究,其目的在于通过深度掌握某个优秀的贯穿前后端的AJAX框架,以提高自己的整体认知感。

1.1.      主要产品特性

抄自: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.

1.2.      初次印象

Buffalo最有价值之初,个人感觉有两点:

1、  后端实现了较为完整的基于xmlxml<->object序列化反序列化协议;

2、  前端提供了适配协议的调用封装和响应解析机制,并基于回调机制提供编码API

 

另,作品受xstream影响颇深,如果看过xstream的代码大家的感觉会更明显,不知道这样说Michael是否有意见:buffalo后端转换器、IO部分的代码,是xstreamlightweight版本:)

2.  Buffalo的关键序列图

要了解buffalo,与其他开源框架一样,最好的实践在于“跑起来”,跟踪并分析其调用执行过程,即可串接其核心的代码组织、逻辑调用关系等细节。

其请求过程的序列图可大概绘制如下:

Ajax框架Buffalo深度研究_第1张图片

概要说明:

1、  整体提供了一个ServletApplicationServlet作为唯一的前后端通讯窗口;通过Servletinit过程初始化配置暴露给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结果,并通过StreamWriterwrapresponse.getOutputStream())写回调用客户端;

7、  客户端通过Buffalo.Reply解析返回的结果,并实现与UI的交互。

3.  Buffalo的协议分析

3.1.      概述

协议就是前后端用以通信的约定,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

3.2.      请求[Buffalo.Call]协议定义

所有的请求,都通过如下格式发出:

buffalo.remoteCall({Service.Method}{Params}{CallBackFunction})

其中:

Service.Method——Service为在buffalo-service.propertiesSpring注册的服务标识;

Params——参数Array,没有参数则定义一个空的数组“[]”(建议对null进行保护);

CallBackFunction——回调函数,提供一个Buffalo.Reply参数

 

最终给服务器端发出的请求,是标准的基于UTF-8XML,格式举例如下:

例子

说明

<buffalo-call>

  <method>sum</method>

  <double>1</double>

  <double>2</double>

</buffalo-call>

 

->这是Service对应的方法

->之后的都是参数,这里表示有两个参数,均为double

 

3.3.      响应[Buffalo.Response]协议定义

返回比较复杂,不同的情况需要区别对待。

3.3.1.         List-数组或列表

Array or java.util.Collection assignable value will be convert to list.

3.3.1.1.      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");

 

 

3.3.1.2.      Array

例子

说明

<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"}

 

3.3.2.         Map & POJO

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);

 

3.3.3.         Ref对象引用

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是否在引用列表中,如果有则使用RefProtocalTag,其值为当前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

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()

 

相关代码如下:

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();

}

 

3.5.      其他

Buffalo还实现了一些例外的对象的协议定义和转换。

3.5.1.         java.sql.Date

例子

说明

<map>

  <type>java.sql.Date</type>

  <string>value</string>

  <date>20061018T211400Z</date>

</map>

注意多了个value,且值是经过特定格式化的

 

3.5.2.         java.math.BigDecimal/ java.math.BigInteger

例子

说明

<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.

3.5.3.         还不够?

那就自己写,实现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开发,这些转换器还是够了的。

4.  核心类分析

4.1.      概述

先来看看代码结构,如下:

Ajax框架Buffalo深度研究_第2张图片

整体而言,与xstream有点像,但考虑到Buffalo是贯穿前后端的Ajax框架,还包括了web/view/request等部分。

代码的核心部分,主要包括了两个部分,分别为serviceprotocol,下面分别重点分析说明。

4.2.      service

此为Buffalo目前实现了的BuffaloWorker为核心的package,主要包括了业务服务Repository、业务服务方法适配定位和业务服务调用等部分。

4.2.1.         服务注册与初始化

核心类图如下:

Ajax框架Buffalo深度研究_第3张图片

服务的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在请求ApplicationServletinit方法中初始化,意味着在第一次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、  以及基于springSpringServiceFactory

 

此时,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);

配置举例如下,此时将取得service1service2两个注册服务并放入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>

 

4.2.2.         服务的匹配与调用

ServiceInvoker是服务调用的借款,只有一个方法,即invode

BuffaloInvoker是其默认实现,通过pathInfo规则确定服务(注册)名称,并通过lookupMethod,确定最佳调用方法,即可完成最终业务服务的invoke

lookupMethod过程考虑了一个matchWeight(匹配权重)问题,其目标是要找到最佳的调用方法。需要注意的是,这个权重的规则并不一定好用。我做了实验,在默认提供的DEMOSimpleService中,增加了几个方法,如下:

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;

    }

此时如果界面中输入12并提交,则返回为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的缓存

Ajax框架Buffalo深度研究_第4张图片

4.3.      protocol

协议在第三章节的“Buffalo的协议分析”中做了详细的描述,这章主要分析代码。

协议部分的代码,总体分为converters(转换器)、io(协议marshal/unmarshal)两个部分,。

从代码的结构和设计技巧可以看出,Michael深入研究过xstream的代码。

4.3.1.         converters(类型转换解析器)

转换器的代码实现了我们WEB开发常用的类型解析,从代码结构分为basiccollectionmap,还有一些exceptional(例外)的类型解析。

Converter的接口定义为:

public interface Converter {   

    boolean canConvert(Class type);

    void marshal(Object source, MarshallingContext marshallingContext, StreamWriter streamWriter);

    Object unmarshal(StreamReader reader, UnmarshallingContext unmarshallingContext);

}

类图罗列如下:

Ajax框架Buffalo深度研究_第5张图片
Ajax框架Buffalo深度研究_第6张图片

 

总而言之,这些代码质量都非常高,设计精巧,合理有效使用了各种设计模式,尤其是Template模式运用的非常精彩。

需要说明的是,转换器可对Array及实现了collection接口的对象进行处理,也可对Map结果的对象进行了完美的支持,而POJO其实在unmarshalMap是一样的而仅仅在marshal上有些特殊。

4.3.2.         io(协议marshal/unmarshal

io这块代码是最复杂的代码之一,涉及到协议解析的很多细节。代码由于解释过少,有些代码我需要反复调试多次才能明白具体意义——可能跟个人能力和资质有关,汗一个。

类图说明如下:

 

Ajax框架Buffalo深度研究_第7张图片Ajax框架Buffalo深度研究_第8张图片

包括五个非常重要的接口,即MarshallingContextUnmarshallingContextMarshallingStrategyStreamReaderStreamWriter。分别如下:

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接口的DefaultMarshallingStrategyunmarshal方法,并通过实现了StreamReaderFastStreamReader解析输入流(为XML),获得必要的目标业务服务的方法和参数,以最终构建BuffaloCall对象,并最终得以调用(可参见“服务的匹配与调用”章节)。

业务服务调用完毕后,将使用实现了MarshallingStrategy接口的DefaultMarshallingStrategymarshal方法,并通过实现了StreamWriterFastStreamWriter输入协议流(为XML)到客户端,实现了XML协议的解析与交换。

FastStreamReader那种一个字符一个字符读进来并解析的过程,我是在鼓足勇气后才通读完毕的,并通过多次调试才理解所有的细节,过程中还补了不少课,善哉善哉。(以后如果要通读xstream,只怕这种勇气得更大更多,^0*

解读过程得益于Buffalo提供的单元测试类,再次感受到TDD的好处咯。

5.  协议可能需要完善的专题讨论

5.1.      深度分析

首先声明:在未与Michael充分沟通交流前,一切都只是个人的想法,未必正确。

本人在“服务的匹配与调用”章节,曾经提出过服务方法匹配的问题,Michaelbuffalo.js310行提到过“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 implementsConverter{

    public boolean canConvert(Class value) {

       if (value == nullreturn 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);

    }

}

显然,在这个例子上,是这个转换器模糊了doubleDouble,统统变成了Double

5.2.      可能的解决方案

当然,这里的解决方案是以“需要这么干”为前提假设的。在很大程度上来说,这种“精确制导”没有必要。

我仅以解决上述例子为目标,做出修缮。

5.2.1.         ProtocalTag中加入新协议标签

位置: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";

    //其他}

 

5.2.2.         定义两个工具类

5.2.2.1.      PrimitiveObjectWrapper

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;

    }  

}

 

5.2.2.2.      PrimitiveObjectUtil

一个获取primitive类型的工具:

package net.buffalo.protocal.util;

 

public class PrimitiveObjectUtil {

    public static Class getPrivitiveType(Object v){

       Class clazz = v instanceof Integer ? int.class :

           instanceof Long ? long.class :

           instanceof Short ? short.class :

           instanceof Byte ? byte.class :

           instanceof Float ? float.class :

           instanceof Double ? double.class :

           instanceof Boolean ? boolean.class :

           v.getClass();

       return clazz;

    }

}

 

5.2.3.         新增转换器PrimitiveDoubleConverter

位置:net.buffalo.protocal.converters.primitive

新增primitivepackage,并建立新的转换器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 AbstractBasicConverterimplements Converter{

 

    public boolean canConvert(Class value) {

       if (value == nullreturn 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));

    }  

}

 

5.2.4.         修改DoubleConverter的协议标签类型

位置:net.buffalo.protocal.converters.basic.DoubleConverter.java

不再是ProtocalTag.TAG_DOUBLE了:

    protected String getType() {

       return ProtocalTag.TAG_DOUBLE_W;

    }

 

5.2.5.         注册转换器到DefaultConverterLookup

位置: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 = newCollectionConverter();

    MapConverter mapConverter = new MapConverter();

    ArrayConverter arrayConverter = new ArrayConverter();

    SqlDateConverter sqlDateConverter = new SqlDateConverter();

    BigNumberConverter bigNumberConverter = newBigNumberConverter();

    ExceptionConverter exceptionConverter = newExceptionConverter();

    ObjectConverter objectConverter = new ObjectConverter();

   

    //新增新的转换器定义

    PrimitiveDoubleConverter primitiveDoubleConverter = newPrimitiveDoubleConverter();

 

    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_NULLnullConverter);

    tagNameConverterCache.put(ProtocalTag.TAG_DATE, dateConverter);

    tagNameConverterCache.put(ProtocalTag.TAG_LIST, collectionConverter);

    tagNameConverterCache.put(ProtocalTag.TAG_MAP, mapConverter);

    tagNameConverterCache.put(ProtocalTag.TAG_REFnewReferenceConverter());

    // 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);

}

 

5.2.6.         修订FastStreamReader

位置: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 == '.');

    }

 

5.2.7.         完善BuffaloCall

位置: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中(注意红色的部分):

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;

       }

 

5.3.      测试结果

输入:

提交:

<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)其他的诸如FloatPrimitive类型的处理,照葫芦画瓢即可;

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

你可能感兴趣的:(ajax框架)