DWR 学习及深入

来源:互联网 发布:淘宝嘉贝逸飞旗舰店 编辑:程序博客网 时间:2024/06/06 03:04
 

DWR 学习及深入

标签: dwrjavascriptajaxtapestryservletcallback
2008-11-04 12:45 2704人阅读 评论(1) 收藏 举报
 分类:
 

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]

1         概述

1.1     AJAX概念

Ajax是现在Web开发者中的术语。我们可以从很多角度来解释这个术语。但是之所以形成了一个术语,是因为它能把用户看到的新东西包括起来。

对于web用户来说得到的全新的功能是可以"页面局部替换" - 可以根据服务器上的数据来更新页面上的一部分内容,而不比刷新整个页面。这种功能在IE和Mozilla上已经存在一段时间了,但是Safari和Konqueror的用户最近才能使用。

就是这种动态改变页面上的内容的功能正在影响着用户和网页的交互方式,例如:

 

 操作

以前的风格

AJAX的风格

在网站中滚动地图

点击向右的箭头,刷新整个页面

把地图向右拖动 - 就能看见地图在滚动

在字典里查单词

输入单词,点击提交,参看单词的定义

开始输入单词,边输入边看到匹配单词,输入结束看到单词定义

在线论坛交互

输入消息,提交,点击"查看新消息"

输入消息,如果有新的回复会自动出现

填写很多字段的表单

访问一个向导的多个页面,得到多个错误字段信息

当你输入完一个字段立即得到错误消息,填写过程中动态的改变数据(例如你填写完zip码,地址上自动填写上地区),而不用等待页面刷新

Ajax不是世界上最好的缩写 - 它代表"Asynchronous JavaScript and XML"。这没有给我们一个很清楚的解释:它背后的技术不一定必须是异步的,也不一定非得用XML来实现。尽管如此,这个术语已经被人们接受了,我们就别计较那么多了。

对于web开发人员来说这是一种很有吸引力的开发web站点的方式,你也可以不用大量的努力就开始做。当然也有很多缺陷会使问题变得很复杂。所有的浏览器都有不同的怪癖,所以你会发现MAC用户已经被关在这次宴会的门外了。

1.2     AJAX页面的设计

你现在已经决定要开发一个Ajax网站,那么你是计划用多页面实现还是就用一个页面呢? 目前为止我已经看见很多关于这方面的整理,所以在这里我根据自己开发的Ajax应用整理了一些要点。

1.2.1    多页面相对简单

多页面实现相对简单有3个原因:

1.它是你习惯的方式

2.在多页面中可以随意的使用

3.书签历史(有后退按钮支持)也很容易实现

1.2.2    可选择的库

在Java领域里有2组Ajax库,我想大多数语言也一样。一组是基于RPC的Ajax库(DWR, JSON-RPC-Java...),它们可以和一些原有的出色JavaScript库Dojo, Script.aculo.us, DWRUtil...)很好的配合。另一种使基于Tag的Ajax库(AjaxTags, AjaxAnywhere...)。

基于RPC的库可以用在任何环境,但是基于Tag的库只能用于多页面模型。所以如果你打算用基于Tag的库,那么就要选择多页面模型。

1.2.3    单页面的设计会带来更好的用户体验

Ajax的一个优点就是更快的加载时间。单页面设计可以使页面更快的展现在用户面前,并且你可以试用类似Script.aculo.us这样的类库来实现一些Fade效果。这样的技术不仅仅是为了吸引眼球,一些研究表明当页面上什么也没发生的时候用户会感觉得等待时间很长,一些简单的动画效果会使用户感觉web应用程序更快,即使事实不是这样。

多数来自于Google(GMail, Reader, Maps), Yahoo (Oddpost), and Microsoft (Kahuna, Start)的设计都是单页面的。其他的是以简化页面的方式(例如Writely.com)。它们都是在提供"用户友好"界面上下功夫。

1.3      Why not Tacos

1.3.1    Tacos简介

Tacos是一个为Tapestry4 编写的 AJAX 框架,借助于dojo的特点和 Tapestry 集中式控制的组件技术特别是它的代码生成,它为Tapestry所编写的页面提供了一个良好的 AJAX 调用的支持——页面的局部刷新。

在编写异步调用的页面时程序员不再需要考虑各种 AJAX 调用的细节,不再需要考虑如何根据返回结果更新页面显示。要做的只是使用异步调用组件并给出需要刷新的组件 ID。它的魔力在于,一旦这些组件被用户或系统本身触发,框架便会自动的调用相应页面和组件,生成显示结果,并最终更新客户端页面内容。对程序员来说,编写的似乎就是普通 Tapestry 页面,而对用户来说更新的只是页面局部的内容。

 在客户端 Tacos 吸纳了 dojo 开发包,即减轻了script 开发的复杂度,又提高了系统的表现能力。在服务器端主要利用了 Tapestry 的代码生成技术和框架自身良好的扩充能力。

1.3.2    Tacos的原理

Tacos的设计思想是“局部更新”。也就是说,对于大部分相同的页面内容保持不变,只更新局部变化内容。这点看似和大多数使用 AJAX  的框架没有什么差别,但是实现的实质是完全不同的。对Tacos来说,服务器每次都会调用整个完整的页面,但是只保留和返回需要更新的局部内容,这样既和普通HTTP请求保持一致的处理方式也达到了局部更新页面的效果。形象地说就是先画一幅和原来差不多的画,然后剪一部分和先前不同的内容贴回去。

这种处理的优势就在于它为 AJAX 调用和普通的 HTTP 请求提供了一个几乎相同的编程环境,屏蔽了所有底层细节。使快速开发维护性高的页面成为可能。

为了达到这个目的,需要服务(service)和组件(component)两方面的支持。服务用来处理所有的异步请求,返回需要更新的现实内容。各个组件会注入一些代码来判断是否需要更新该组件的代码。这样,对于需要跟新的组件会使用一个实际的 writer 来输出结果,而对于不需要等新的组件使用空的 writer,产生的结果立即被抛弃。

1.3.3    Tacos的缺点

1.           编程难度大

Tacos这种编程方式完全不同于普遍使用的 AJAX 编程方式,需要一点时间适应。特别是当需要达到某些特殊效果是更需要对Tapestry机制有比较深入的理解。

2.           通讯速度慢

Tacos为了得到局部内容而生成整个页面,严重浪费了服务器资源,加重了一些服务器负担(可以参照Tacos原理中的灰色部分)。

在比较DWR的测试中,可以看到Tacos比DWR慢了5倍速度以上。

3.           并非真正意义上的异步

Tacos文档中提到的一个关键性词语是“局部刷新”而不是“异步刷新”,基于Tacos的特性我们可以看出Tacos并不是一个真正意义上的异步AJAX框架,而是巧妙的回避了异步提交这个问题。

在Tacos的DEMO中有很多例子表现了这一缺点,当一个请求发出后,没有收到服务器回应之前,浏览器中的鼠标将会变成一个漏斗状,说明客户端在等待服务器的回答,这足以证明Tacos的请求是基于同步的。在异步的AJAX框架中,不应该出现等待服务器回调的状态,一个请求被客户端发出后,服务器是否做出响应,何时响应都与客户端不再有关系,而Tacos显然没有做到这一点。

1.4     DWR简介

DWR是一个Java开源库,帮助你实现Ajax网站。

它可以让你在浏览器中的JavaScript代码调用Web服务器上的Java,就像在Java代码就在浏览器中一样。

DWR主要包括两部分:

ü       在服务器上运行的Servlet来处理请求并把结果返回浏览器。

ü       运行在浏览器上的Javascript,可以发送请求,并动态改变页面。DWR会根据你的Java类动态的生成Javascript代码。这些代码的魔力是让你感觉整个Ajax调用都是在浏览器上发生的,但事实上是服务器执行了这些代码,DWR负责数据的传递和转换。

这种Java和JavaScript之间的远程调用会让DWR用户感觉像是曾经习惯使用的RMI或SOAP的RPC机制。而且这一过程还不需要额外的浏览器插件。

Java是同步的,而Ajax是异步的。所以当你调用一个远程方法时,你要给DWR一个回调函数,当数据从网络上回来时,DWR会调用这个函数。


这个图表现了DWR是如何在onclick事件中改变下拉列表的内容的。

DWR动态地为服务端AjaxService类(Java)生成了一个相应的客户端AjaxService类(Javascript)。这个类被eventHandler调用。DWR就会去处理整个远程调用的细节,包括在JavaScript和Java之间转换参数和返回值。然后在这里例子中,它会执行你提供的回调函数(populateList),这个函数再利用DWR提供的工具函数来更改页面内容。

DWR帮你生产出具有很好交互性的网站,它提供的一些JavaScript库帮你处理DHTML,也提供了一些例子作为参考。

1.4.1    使用DWR的站点

使用DWR技术的比较知名的站点有:

Walmart

http://www.walmart.com/

New York City Maps

http://www.nyc.gov/citymap

JIRA and Confluence

http://atlassian.com/

DZone

http://www.dzone.com/

InfoQ

http://www.infoq.com/

Pebble

http://pebble.sourceforge.net/

Bodog

http://www.bodog.com/horse-betting/

 

 

2         快速上手

2.1     基本配置

想要快速的使用DWR框架,只需要按照以下三个步骤来进行配置。

1.      安装DWR的Jar包

下载dwr.jar文件。把它放到你的webapp的WEB-INF/lib目录下。

2.      编辑配置文件

需要把下面的代码加到WEB-INF/web.xml文件中。

<servlet>那部分需要和其他的<servlet>在一起,<servlet-mapping>部分也一样。

<servlet>

  <servlet-name>dwr-invoker</servlet-name>

  <display-name>DWR Servlet</display-name>

  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>

  <init-param>

     <param-name>debug</param-name>

     <param-value>true</param-value>

  </init-param>

</servlet>

 

<servlet-mapping>

  <servlet-name>dwr-invoker</servlet-name>

  <url-pattern>/dwr/*</url-pattern>

</servlet-mapping>

在WEB-INF目录下的web.xml旁边创建一个dwr.xml文件。可以从最简单的配置开始:

<!DOCTYPE dwr PUBLIC

    "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"

    "http://www.getahead.ltd.uk/dwr/dwr10.dtd">

 

<dwr>

  <allow>

    <create creator="new" javascript="JDate">

      <param name="class" value="java.util.Date"/>

    </create>

    <create creator="new" javascript="Demo">

      <param name="class" value="your.java.Bean"/>

    </create>

  </allow>

</dwr>

DWR配置文件定义了那些DWR会创建提供远程调用的JavaScript类。在上面的例子中我们定义了两个类来提供远程调用,并为其提供的JavaScript类的名字。

在上面我们使用了new创建器,它会调用没有参数的构造函数来创建实例,但是所有JavaBean必须有这一构造函数。

在上面的配置中,我们告诉DWR需要将java.util.Date和your.java.Bean这两个类分别生成命名为:JDate.js和Demo.js的JS文件。这个JS文件将在以后用于你的JavaScript与这两个JS实际对应的JAVA类进行通讯。

还要注意DWR有一些限制:

  • 不要出现JavaScript保留关键字;和保留关键字同名的函数指定被排除。多数JavaScript的关键字和Java是相同的。所以你不可能有一个方法叫做"try()"。但是要注意"delete()"对与JavaScript有着特殊意义,而对Java则不是。
  • JavaScript是不支持方法重载的,所以尽量不要在DWR中使用。

3.       访问下面的URL

http://localhost:8080/[YOUR-WEBAPP]/dwr/

你可以看见一个页面,里面有第二步中的类。接着往里点,你会看到所有可以调用的方法列表。这个页面是动态生成用来测试的例子。

你可以通过在这个页面点击鼠标右键,选择:查看源代码来了解DWR的部分代码。

2.2     编写自己的代码

例如,我们想实现如图的功能:

这是一个普通的页面,包括一个按钮,以及一个<span>标签,用于显示我们点击按钮后从远端服务器拿到的信息。这个HTML效果的代码如下:

<head>
<script type='text/javascript' src='/dwr-demo/dwr/interface/Demo.js'> </script>

<script type='text/javascript' src='/dwr-demo/dwr/engine.js'> </script>

<script type='text/javascript' src='/dwr-demo/dwr/util.js'> </script>

</head>

 

<body>

<h2>Demo</h2>

 

<p>Query Server: <input value="Execute" type="button" onclick="update()"/>

<br/>Server Information: <span id="reply" style="background:#eeffdd"></span></p>
<body>

需要注意这里有三个被引用的JS文件,你可以在浏览器中访问这三个JS,但是不要试图在你的WebRoot目录中寻找它们,因为第一个文件是DWR根据之前的配置文件dwr.xml动态生成的,第二个与第三个则是存在于dwr.jar的包中。

当按钮被点击的时候将会响应名为update()的JS方法,这个方法的定义如下:

function update()

{

    Demo.getServerInfo(loadinfo);

}

整个方法只有一句,就是告诉DWR,调用Demo类的getServerInfo()方法,并且回调方法为loadinfo()。需要注意的是这里的loadinfo并不是通常JS中传递给方法的参数,而是一个回调方法的申明,通常情况下,每一个远程调用都需要一个回调方法,因为DWR是异步(可以指定同步,默认方式下为异步)传输数据,所以用户的浏览器将不会等待一个请求发出后的返回,那么就必然需要一个回调函数来处理返回后的事情,除非你的业务根本不会返回(void)。

  • 如果想传递参数给JAVA类的话,你可以把回调申明放在第一个参数位或者最后一个参数位。DWR将会自动识别。

我们的JAVA类:your.java.Bean中同时应该有这样一个方法:

public String getServerInfo()

{

    return WebContextFactory.get().getServletContext().getServerInfo()

           + " running on JDK "

           + System.getProperty("java.specification.version")

           + " using DWR "

           + ExecutionContext.get().getVersion();

}

在Tomcat中,我们就可以通过访问之前写的HTML页面来观察我们的第一个服务器端AJAX的效果了。

3         DWR详细配置

3.1     Web.xml配置

3.1.1    基本配置

通常情况下,我们需要使用DWR的时候只需要对web.xml做最简单的配置:

<servlet>

  <servlet-name>dwr-invoker</servlet-name>

  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>

</servlet>

<servlet-mapping>

  <servlet-name>dwr-invoker</servlet-name>

  <url-pattern>/dwr/*</url-pattern>

</servlet-mapping>

此外还可以加入一些重要和有用的参数。

3.1.2    Loging

DWR可以工作在JDK1.3上,而JDK1.3不支持java.util.logging,但是DWR不想强迫任何人使用commons-logging或者log4j,所以当没有logging类的时候DWR就使用HttpServlet.log()方法。如果DWR发现了commons-logging,就使用它。

1.1.1.1     Commons-Logging

几乎每一个人都在使用commons-logging,因为大多数的servlet容器在使用它。所以如果你的web应用中没有明显的加入commons-logging包,它也会默认的配置好。

在这种情况下,logging是由java.util.logging或者log4j配置文件控制的。详细配置查看Log4j文档。

1.1.1.2     HttpServlet.log()

如果你用HttpServlet.log(), 下面的配置控制logging:

<init-param>

<param-name>logLevel</param-name>

<param-value>DEBUG</param-value>

</init-param>

可用的值有:FATAL, ERROR, WARN (默认), INFO 和 DEBUG。

3.1.3    多个dwr.xml文件 和 J2EE安全

一般来说,你只需要一个dwr.xml文件,并且放置在默认的位置:WEB-INF/dwr.xml。 如果那样的话,你可以不用了解下面的配置。

有三个原因使你希望指定不同位置的dwr.xml文件。

ü                     你希望让dwr.xml文件和它能访问到的资源在一起。在这种情况下你需要一个这样的配置: <param-value>WEB-INF/classes/com/yourco/dwr/dwr.xml</param-value> 。

ü                     你有大量的远程调用类,希望把他们分成多个文件。在这种情况下你需要重复下面的配置几次,每一个中有不同的 param-name,并且以 &apos;config&apos; 开头。DWR会依次把他们都读进来。

ü                     DWR可以使用Servlet规范的J2EE的URL安全机制来给不同的用户不同的访问权限。你只需要简单的定义多个dwr servlet,并且制定不同的名字,url和访问权限。

如果你希望使用这一功能,那么语法是这样的:

<init-param>

<param-name>config*****</param-name>

<param-value>WEB-INF/dwr.xml</param-value>

<description>What config file do we use?</description>

</init-param>

在这里config*****意思是param-name要以字符串&apos;config&apos;开头。这个参数可以根据需要使用多次,但是不能相同。

一个使用J2EE的安全机制的例子:

<servlet>

<servlet-name>dwr-user-invoker</servlet-name>

<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>

<init-param>

<param-name>config-user</param-name>

<param-value>WEB-INF/dwr-user.xml</param-value>

</init-param>

</servlet>

<servlet>

<servlet-name>dwr-admin-invoker</servlet-name>

<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>

<init-param>

<param-name>config-admin</param-name>

<param-value>WEB-INF/dwr-admin.xml</param-value>

</init-param>

</servlet>

<servlet-mapping>

<servlet-name>dwr-admin-invoker</servlet-name>

<url-pattern>/dwradmin/*</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>dwr-user-invoker</servlet-name>

<url-pattern>/dwruser/*</url-pattern>

</servlet-mapping>

 

<security-constraint>

<display-name>dwr-admin</display-name>

<web-resource-collection>

<web-resource-name>dwr-admin-collection</web-resource-name>

<url-pattern>/dwradmin/*</url-pattern>

</web-resource-collection>

<auth-constraint>

<role-name>admin</role-name>

</auth-constraint>

</security-constraint>

<security-constraint>

<display-name>dwr-user</display-name>

<web-resource-collection>

<web-resource-name>dwr-user-collection</web-resource-name>

<url-pattern>/dwruser/*</url-pattern>

</web-resource-collection>

<auth-constraint>

<role-name>user</role-name>

</auth-constraint>

</security-constraint>

3.1.4    使用debug/test模式

你可以通过下面的参数让DWR进入debug/test模式:

<init-param>

<param-name>debug</param-name>

<param-value>true</param-value>

</init-param>

在debug模式里,DWR会为每一个远程调用类生成一个测试页面。这对于检查DWR是否工作和工作的怎么样是很有用的。这个模式还可以警告你一些存在的问题:JavaScript保留字问题,或者函数重载问题。

尽管如此,这个模式不应该使用在实际部署环境里面,因为它可以为攻击者提供你的服务的大量信息。如果你的网站设计的好的话,这些信息不会帮助攻击者窥视你的网站内容,但是还是不要给任何人一个找到你错误的机会。

3.1.5    使用插件(Plug-in)

DWR里的很多部件都是可插入的,所以所以可以通过替换掉DWR的默认实现类来改变其功能。你可以在 <init-param> 中的 param-name 中指定你要替换的接口,并在 param-value 中指定自己的接口实现类。

可插入点是:

uk.ltd.getahead.dwr.AccessControl

uk.ltd.getahead.dwr.Configuration

uk.ltd.getahead.dwr.ConverterManager

uk.ltd.getahead.dwr.CreatorManager

uk.ltd.getahead.dwr.Processor

uk.ltd.getahead.dwr.ExecutionContext

这些可插入点默认的实现都在uk.ltd.getahead.dwr.impl中。

3.2     Dwr.xml配置

dwr.xml是DWR的配置文件。默认情况下,应该把它放到WEB-INF目录(web.xml的目录)下。

3.2.1    创建dwr.xml文件

dwr.xml文件的结构如下:

<!DOCTYPE dwr PUBLIC

    "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"

    "http://www.getahead.ltd.uk/dwr/dwr10.dtd">

<dwr>

  <!-- init is only needed if you are extending DWR -->

  <init>

    <creator id="..." class="..."/>

    <converter id="..." class="..."/>

  </init>

  <!-- without allow, DWR isn't allowed to do anything -->

  <allow>

    <create creator="..." javascript="..."/>

    <convert converter="..." match="..."/>

  </allow>

  <!-- you may need to tell DWR about method signatures -->

  <signatures>

    ...

  </signatures>

</dwr>

3.2.2     术语解释

这里是一些必须理解的术语 - 参数会被converted,远程Bean会被created。所以如果你有一个叫A的bean,它有一个方法叫A.blah(B) 那么你需要一个A的creator和一个B的converter。

1.1.1.3     <allow>

allow段落里面定义的试DWR可以创建和转换的类。

1.1.1.4     Creators

我们要调用的每个类都需要一个<create ...>定义。creator有几种。比较通用的是new关键字和Spring。

1.1.1.5     Converters

我们必须保证所有的参数都可以被转换。JDK中的多数类型已经有转换器了,但是你需要给DWR转换你的代码的权利。一般来说JavaBean的参数需要一个<convert ...>定义。

默认情况下,如下类型不需要定义就可以转换:

所有的原生类型 boolean,int,double, 等等

原生类型的对象类型 Boolean,Integer,等等

java.lang.String

java.util.Date 和SQL中的Date

以上类型组成的数组

以上类型的集合类型 (Lists, Sets, Maps, Iterators, 等)

从DOM, XOM, JDOM 和 DOM4J中的DOM对象 (类似 Element 和 Document)

1.1.1.6     <init>

可选的init部分用来声明创造bean的类和转换bean的类。多数情况下你不需要用到他们。如果你需要定义一个新的Creator 和 Converter, 那么你就需要在这里定义他们。但是建议你现检查一下DWR是不是已经支持了。

在init部分里有了定义只是告诉DWR这些扩展类的存在,给出了如何使用的信息。这时他们还没有被使用。这中方式很像Java中的import语句。多数类需要在使用前先import一下,但是只有import语句并不表明这个类已经被使用了。每一个creator和converter都用id属性,以便后面使用。

1.1.1.7     <signatures>

DWR使用反射来找出在转换时应该用那种类型。有时类型信息并不明确,这时你可以在这里写下方法的签名来明确类型。

Signatures一般只有当一种情况的时候才会使用,那就是你的远程方法中存在“范型”的参数的时候,即JAVA 1.5 API中定义的范型数据。

例如:你的Demo类有这样的方法:testBeanListParam(List<TestBean>); 它的参数为一个范型的List,那么你就需要在Signature中定义:

<signatures>

  <![CDATA[

  import java.util.*;

  import uk.ltd.getahead.dwrdemo.test.*;

 

  Test.testBeanListParam(List<TestBean>);

  ]]>

  </signatures>

3.2.3    延时回调

当我们测试我们写的页面AJAX程序时,可能需要考虑AJAX的异步回调时间,因为大多数情况下,服务器不可能立即响应浏览器的请求,然后发起回调,在正常情况下,回调的时间是不可确定的。所以在测试时我们可以尽量延长DWR发起回调的时间来模拟网络状态不好,或者服务器繁忙时候的响应情况。通过在dwr.xml的<allow>中添加如下的参数可以实现让DWR延时回调的功能:

<filter class="org.directwebremoting.filter.ExtraLatencyAjaxFilter">

      <param name="delay" value="5000"/>

</filter>

 

4         如何编写客户端脚本

4.1     书写规范

在说如何编写一个DWR的AJAX之前先讲一下DWR的一些规范,在DWR2.0之前的版本里,我们如果需要使用engine.js或者util.js中的方法,我们需要这样调用:

DWREngine.setTimeout(1000);

DWRUtil.setValue(…);

而在DWR2.0之后,这种写法变成了:

dwr.engine.setTimeout(1000);

dwr.util.setValue(…);

但是为了向下兼容,所以目前仍然保留了1.0的写法。这两种写法在DWR 2.0 RC4版本中都可以使用。

4.2     DWR脚本简介

DWR本身根据dwr.xml生成和Java代码类似的JavaScript代码。

相对于Java的同步调用,创建与Java代码匹配的Ajax远程调用接口的最大挑战来自于实现Ajax的异步调用特性。DWR通过引入回调函数来解决这个问题,当结果被返回时,DWR会调用这个回调函数。

有两种推荐的方式来使用DWR实现远程方法调用:1.可以通过把回调函数放在参数列表里,2.也可以把回调函数放到元数据对象里。

4.2.1    编写回调函数

1.1.1.8     简单的回调函数

假设你有一个这样的Java方法:

public class Remote {

    public String getData(int index) { ... }

}

我们可以在Javascript中这样使用:

<script type="text/javascript"

    src="[WEBAPP]/dwr/interface/Remote.js"</script>

<script type="text/javascript"

    src="[WEBAPP]/dwr/engine.js"</script>

...

 

function handleGetData(str) {

  alert(str);

}

 

Remote.getData(42, handleGetData);

42是Java方法getData()的一个参数,而handleGetData是回调函数的名称。

此外你也可以使用这种减缩格式:

Remote.getData(42, function(str) { alert(str); });

 

  • 官方推荐的申明一个回调函数的方式是将回调函数名称放在参数的最后一位,DWR会自动识别并正确的调用回调函数(前提是回调函数必须存在)。

1.1.1.9     使用元数据对象的回调函数

另外一种语法时使用"调用元数据对象"来指定回调函数和其他的选项。上面的例子可以写成这样:

Remote.getData(42, {

  callback:function(str) { alert(str); }

});

这种方法有很多优点:易于阅读,更重要的指定额外的调用选项。

1.1.1.10 传递额外数据到回调函数

很多时候你会希望能够传递更多的信息给回调函数,以便在回调函数中做一些后续的处理,这时候你需要将回调函数写的更复杂一些。

例如,你希望回调函数不止接受从远端服务器传递回来的一个参数,同时还想接受其他多个JS参数的时候,你需要这样写:

function callbackFunc(dataFromServer, dataFromBrowser) {

  // do something with dataFromServer and dataFromBrowser ...

}

这是你希望的回调函数的样子,那么你在调用时应该写:

var dataFromBrowser = ...;

 

// define an erasure function to store a reference to

// dataFromBrowser and to call dataFromServer

var callbackProxy = function(dataFromServer) {

  callbackFunc(dataFromServer, dataFromBrowser);

};

 

var callMetaData = { callback:callbackProxy };

 

Remote.method(params, callMetaData);

这里,我们用一个回调代理函数解决了上面的需求。

更加清晰易懂的写法是这样:

var dataFromBrowser = ...;

Remote.method(params, {

  callback:function(dataFromServer) {

    callbackFunc(dataFromServer, dataFromBrowser);

  }

});

4.2.2    超时和错误处理

在回调函数的元数据中你可以指定超时和错误的处理方式。例如:

Remote.getData(42, {

  callback:function(str) { alert(str); },

  timeout:5000,

  errorHandler:function(message) { alert("Oops: " + message); }

});

4.2.3    创造一个与Java对象匹配的JavaScript对象

假设你有这样的Java方法:

public class Remote {

  public void setPerson(Person p) {

    this.person = p;

  }

}

Person对象的结构是这样的:

public Person {

  private String name;

  private int age;

  private Date[] appointments;

  // getters and setters ...

}

那么你可以在JavaScript中这样写:

var p = {

  name:"Fred Bloggs",

  age:42,

  appointments:[ new Date(), new Date("1 Jan 2008") ]

};

Remote.setPerson(p);

在JavaScript没有出现的字段,在Java中就不会被设置。

因为setter都是返回void,我们就不需要使用callback函数了。如果你想要一个返回void的服务端方法的完整版,你也可以加上callback函数。但是很明显DWR不会向它传递任何参数。

需要注意的是:你需要在dwr.xml中加入对Person这个类的转换器,如下所示。

    <convert converter="bean" match="com.ce.Person"></convert>

当你的远程调用中使用到自定义的类型的时候,都需要在dwr.xml中,像这样进行声明,否则DWR将无法正确的传递这个参数。

4.3     使用DWR内建JavaScript工具

4.3.1    engine.js

engine.js对DWR非常重要,因为它是用来转换来至动态生成的接口的javascript函数调用的,所以只要用到DWR的地方就需要它。

每一个页面都需要下面这些语句来引入主DWR引擎。

<script type="text/javascript" src="/[YOUR-WEB-APP]/dwr/engine.js">

</script>

1.1.1.11 使用选项

下面这些选项可以通过 DWREngine.setX() 函数来设置全局属性。例如:

DWREngine.setTimeout(1000);

或者在单次调用级别上(假设Remote被DWR暴露出来了):

Remote.singleMethod(params, {

callback:function(data) { ... },

timeout:2000

});

远程调用可以批量执行来减少反应时间。endBatch 函数中可以设置选项。

DWREngine.beginBatch();

Remote.methodInBatch1(params, callback1);

Remote.methodInBatch2(params, callback2);

DWREngine.endBatch({

timeout:3000

});

可以混合这几种方式,那样的话单次调用或者批量调用级别上的设置可以复写全局设置。当你在一个批量处理中多次设置了某个选项,DWR会保留最后一个。所以如果 Remote.singleMethod() 例子在batch里面,DWR会使用3000ms作为超时的时间。

callback和exceptionHandler两个选项只能在单次调用中使用,不能用于批量调用。

preHook和postHook选项两个选项是可添加的,就是说你可以为每一次调用添加多个hook。全局的preHook会在批量调用和单次调用之前被调用。同样全局的postHook会在单次调用和批量调用之后被调用。

1.1.1.12 选项索引

DWREngine中还包括了如下的一些选项,你可以在实际的应用中选择使用:

通用:

Option

Global

Batch

Call

Summary

async

1.1

1.1

1.1

设置是否为异步调用,不推荐同步调用

headers

2.0

2.0

2.0

在XHR调用中加入额外的头信息

parameters

2.0

2.0

2.0

可以通过Meta-datarequest.getParameter()取得的元数据

httpMethod

2.0

2.0

2.0

选择GET或者POST. 1.x中叫&apos;verb&apos;

rpcType

2.0

2.0

2.0

选择是使用xhr, iframe或者script-tag来实现远程调用. 1.x中叫&apos;method&apos;

skipBatch

1.0*

2.1?

-

某个调用是否应该设置为batch中的一部分或者直接的。这个选项和上面都有些不同。
*没有setSkipBatch()方法,批量调用是通过beginBatch()和endBatch()来控制的。

timeout

1.0

1.1

1.1

设定超时时长,单位ms

处理器(Handler)

Option

Global

Batch

Call

Summary

errorHandler

1.0

1.1

1.1

当出了什么问题时的动作。1.x中还包括服务端的异常。从2.0开始服务端异常通过&apos;exceptionHandler&apos;处理

warningHandler

1.0

2.0

2.0

当因为浏览器的bug引起问题时的动作,所以默认这个设置为null(关闭)

textHtmlHandler

2.0

2.0

2.0

当得到不正常的text/html页面时的动作(通常表示超时)

调用处理器(Call Handler) (注册到单独调用上的,而不是batch中的所有调用)

Option

Global

Batch

Call

Summary

callback

-

-

1.0

调用成功以后的要执行的回调函数,应该只有一个参数:远程调用得到的数据

exceptionHandler

-

-

2.0

远程调用失败的动作,一般是服务端异常或者数据转换问题。

Hooks (一个batch中可以注册多个hook)

Option

Global

Batch

Call

Summary

preHook

1.0

1.1

1.1

远程调用前执行的函数

postHook

1.0

1.1

1.1

远程调用后执行的函数

全局选项(在单次调用或者批量调用中不可用)

Option

Global

Batch

Call

Summary

ordered

1.0

-

-

DWR是否支持顺序调用

pollType

2.0

-

-

选择xhr或者iframe的反转Ajax

reverseAjax

2.0

-

-

是否查找inbound调用

4.3.2    util.js

util.js包含了一些工具函数来帮助你用JavaScript数据(例如从服务器返回的数据)来更新你的web页面。

你可以在DWR以外使用它,因为它不依赖于DWR的其他部分。

util.js有4个基本的操作页面的函数:getValue[s]()和setValue[s]()可以操作大部分HTML元素除了table,list和image。getText()可以操作select list。

要修改table可以用addRows()和removeAllRows()。要修改列表(select列表和ul,ol列表)可以用addOptions()和removeAllOptions()。

1.1.1.13 $()和byId()

$() 函数(它是合法的JavaScript命名) 是从Prototypes(一个JS框架)“偷”来的主意。大致上讲:

$ = document.getElementById

因为在Ajax程序中,你会需要写很多这样的语句,所以使用 $() 会更简洁。

通过指定的id来查找当前HTML文档中的元素,如果传递给它多个参数,它会返回找到的元素的数组。所有非String类型的参数会被原封不动的返回。这个函数的灵感来源于prototype库,但是它可以在更多的浏览器上运行。

byId()的作用跟$()完全一样。

使用$()来代替document.getElementById()可以使你的JS代码更加的清晰和便于阅读。

1.1.1.14 setEscapeHtml()

这个方法接受一个boolean的参数,用于告诉DWR是否解析变量中的HTML标签,默认为true,即不解析,这时你的变量中的HTML标签将原样的显示出来,如果想要这些HTML被浏览器执行并解析,需要dwr.util.setEsxapeHtml(false)。

1.1.1.15 addOptions and removeAllOptions填充下拉菜单

AJAX中的一个常见的任务就是根据数据填充下拉菜单。下面的例子就是根据输入填充列表。

<select id="demo1"> </select>

<input type="button" onclick="dwr.util.addOptions( 'demo1', [ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ]) "

value="dwr.util.removeAllOptions('demo1')"/>

 

<input type="button" onclick="dwr.util.removeAllOptions('demo1')" value="dwr.util.removeAllOptions('demo1')"/>

上面的例子展示了一个动态的为一个<select>下拉菜单添加内容的程序,请注意红色字体的部分,addOptions用于将一个数组中的每一个元素添加到指定id的下拉菜单中。

removeAllOptions则用于删除指定id的列表中的所有内容。

ü                     使用对象数组&对象列表填充下拉菜单

如果你的远程JavaBean中的方法返回了一个对象的列表:List<Person>,Person类中有name,age两个属性,而我们只想在我们的下拉菜单中显示所有人的name而不想显示age属性,我们需要编写如下的代码:

<select id="demo2"> </select>

dwr.util.addOptions( "demo2",[

  { name:'Afr', age:'18' },

  { name:'Ame', age:'19' },

  { name:'Asi', age:'20' },

  { name:'Aus', age:'21m' },

  { name:'Bule', age:'10' }

],name);

这里我们使用一个JSON的数组格式模拟从Java Bean中返回的List<Person>,执行完这段代码后,demo2的下拉菜单中将只会显示所有name字段的内容。在这个例子中,由于没有指定value,所以下拉菜单demo2中的每一个选项的name值等于value值。

ü                     指定value的对象列表填充

addOptions还有一种用法用于给下拉菜单的每一个选项添加一个不用的value:

<select id="demo3"> </select>

dwr.util.addOptions( "demo3",[

  { name:'Afr', age:'18' },

  { name:'Ame', age:'19' },

  { name:'Asi', age:'20' },

  { name:'Aus', age:'21m' },

  { name:'Bule', age:'10' }

], age, name);

这时,倒数第二个参数标识了每一个选项的value来源,在选择了某一选项并且提交表单后,这个value会被提交给指定的服务。

ü                     使用Map填充一个下拉列表

有时候你需要使用Map来填充一个下拉菜单,这时候需要使用以下的形式:

dwr.util.addOptions( "demo4", {

  AF:'Africa',

  AM:'America',

  AS:'Asia',

  AU:'Australasia',

  EU:'Europe'

} , age, name);

这里使用了JSON的格式来模拟一个Map的键-值对,实际上当一个Map传递给JavaScript的时候也正是这种格式。

如果你希望在你更新了select以后,它仍然保持运来的选择,你要像下面这样做:

var sel = DWRUtil.getValue(id);

DWRUtil.removeAllOptions(id);

DWRUtil.addOptions(id, ...);

DWRUtil.setValue(id, sel);

如果你想加入一个初始的"Please select..." 选项那么你可以直接加入下面的语句:

DWRUtil.addOptions(id, /["Please select ..."]);

然后再下面紧接着加入你真正的选项数据。

另外,addOptions同样可以用于给<ul><ol>标签生成的列表添加数据。

1.1.1.16 addRows and removeAllRows

DWR通过这两个函数来帮你操作table: DWRUtil.addRows() 和 DWRUtil.removeAllRows() 。这个函数的第一个参数都是table、tbody、thead、tfoot的id。一般来说最好使用tbody,因为这样可以保持你的header和footer行不变,并且可以防止Internet Explorer的bug。

语法:

DWRUtil.removeAllRows(id);

通过id删除table中所有行。

DWRUtil.addRows(id, array, cellfuncs, [options]);

向指定id的table元素添加行。它使用数组中的每一个元素在table中创建一行。然后用cellfuncs数组中的没有函数创建一个列。单元格是依次用cellfunc根据没有数组中的元素创建出来的。

DWR1.1开始,addRows()也可以用对象做为数据。如果你用一个对象代替一个数组来创建单元格,这个对象会被传递给cell函数。

你可以写一些像这样的伪代码:

for each member in array

  for each function in cellfuncs

    create cell from cellfunc(array[i])

参数:

ü                     id: table元素的id(最好是tbody元素的id)

ü                     array: 数组(DWR1.1以后可以是对象),做为更新表格数据。

ü                     cellfuncs: 函数数组,从传递过来的行数据中提取单元格数据。

ü                     options: 一个包含选项的对象(见下面)

选项包括:

ü                     rowCreator: 一个用来创建行的函数(例如,你希望个tr加个css). 默认是返回一个document.createElement("tr")

ü                     cellCreator: 一个用来创建单元格的函数(例如,用th代替td). 默认返回一个document.createElement("td")

例如,有这样一个<table>

<table border="1">

<thead>

<tr>

 

<th>Unaltered</th>

<th>Altered</th>

<th>Button</th>

<th>Count</th>

 

</tr>

</thead>

<tbody id="demo1"> </tbody>

</table>

我们想给这个table添加一些数据,需要如下的代码:

var cellFuncs = [

  function(data) { return data; },

  function(data) { return data.toUpperCase(); },

  function(data) {

    return "<input type='button' value='Test' onclick='alert(/"Hi/");'/>";

  },

  function(data) { return count++; }

];

 

var count = 1;

dwr.util.addRows( "demo1", [ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ] , cellFuncs);

这段程序将会把红色字体的数组以cellFuncs中定义的方式添加进ID为demo1的tbody中。

当然,你也可以自定义每一个单元格的样式,使用如下的代码:

dwr.util.addRows( "demo2", [ 'Africa', 'America', 'Asia', 'Australasia', 'Europe' ] , cellFuncs, {

  rowCreator:function(options) {

    var row = document.createElement("tr");

    var index = options.rowIndex * 50;

    row.style.color = "rgb(" + index + ",0,0)";

    return row;

  },

  cellCreator:function(options) {

    var td = document.createElement("td");

    var index = 255 - (options.rowIndex * 50);

    td.style.backgroundColor = "rgb(" + index + ",255,255)";

    td.style.fontWeight = "bold";

    return td;

  }

});

上面的代码使用了自定义的document.createElement("tr");来创建一个自定义的tr并为它设定样式,背景色等属性。

rowCreator与cellCreator的options具有以下的一些属性:

ü                     rowData: the element value from the array (the same for all cells in a row)

ü                     rowIndex: the key (if map) or index (if array) from the collection

ü                     rowNum: The row number counting from 0 in this section (so if you are using tbody, it counts rows in the tbody and not the whole table)

ü                     data: The 'computed' data value for the cell (cellCreators only)

ü                     cellNum: The cell number that we are altering counting from 0 (cellCreators only)

1.1.1.17 setValue(id, value) getValue(id)

DWRUtil.setValue(id, value)根据第一个参数中指定的id找到相应元素,并根据第二个参数改变其中的值。

这个函数能操作大多数HTML元素包括select(去选择指定的value的选项)、input元素(包括textarea)、div和span。

DWRUtil.getValue(id)是 setValue()对应的"读版本"。它可以从HTML元素中取出其中的值,而你不用管这个元素是select列表还是一个div。

这个函数能操作大多数HTML元素包括select(去处当前选项的值而不是文字)、input元素(包括textarea)、div和span。

1.1.1.18 getText(id)

getText(id)和getValue(id)很相似。它是为select列表设计的。你可能需要取得显示的文字,而不是当前选项的值的时候就需要用getText。

1.1.1.19 getValues()

getValues()和getValue()非常相似,除了输入的是包含name/value对的javascript对象。name是HTML元素的ID,value会被更改为这些ID对象元素的内容。这个函数不会返回对象,它只更改传递给它的值。

1.1.1.20 DWRUtil.onReturn

当在表单中,填写完一个input区域后按下回车键时,得到响应。

当表单中有input元素,按下回车键会导致表单被提交。当使用Ajax时,这往往不是你想要的。而通常你需要的触发一些JavaScript。

不幸的是不同的浏览器处理这个事件的方式不一样。所以DWRUtil.onReturn修复了这个差异。如果你需要一个同表单元素中按回车相同的特性,你可以用这样代码实现:

<input type="text" onkeypress="DWRUtil.onReturn(event, submitFunction)"/>

<input type="button" onclick="submitFunction()"/>

你也可以使用onkeypress事件或者onkeydown事件,他们做同样的事情。

1.1.1.21 DWRUtil.selectRange

这个方法用于选择一个输入框中的一定范围的文字。

例如,有这样一个输入框:

它的Id为:sel-test,那么我们可以使用selectRange方法来使得其中某些字符选中:

DWRUtil.selectRange("sel-test",4,8);

执行后得到如下的效果:

1.1.1.22 DWRUtil.useLoadingMessage

这个语句用于设置一个Gmail风格的加载信息。当客户端通过DWR远程调用服务器的时候就会显示出来。

最简单的用法是这样:

<head>

  <script>

  function init() {

    DWRUtil.useLoadingMessage();

  }

  </script>

  ...

</head>

<body onload="init();">

5         控制反转AJAX(Reverse Ajax

5.1     概述

控制反转(Reverse Ajax)是DWR的一个十分独特的功能,它的概念来源于Spring中的控制反转。

控制反转的概念很容易理解:假设我们的WEB有一个活动提醒的功能,网站管理员在添加了一条指定时间的活动后,当时间到达,需要给所有的网页浏览者发一条通知信息。以往我们的做法是:在用户登录后就将数据库中所有的活动读取出来并且以JavaScript的形式保存在客户端,判断时间以及提醒用户都是由JavaScript来进行完成,而现在,通过控制反转,我们可以把这一项功能交给服务器来实现。

5.2     简单的例子

我们来看一个上述需求的简化版本的例子,我们写一个页面如下:

<html>

  <head>

    <title>ReverseServerTimerTigger</title>

    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

    <meta http-equiv="description" content="this is my page">

    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

 

   <script type='text/javascript' src='dwr/interface/ServerTimerTigger.js'> </script>

   <script type='text/javascript' src='dwr/engine.js'> </script>

   <script type='text/javascript' src='dwr/util.js'> </script>

  </head>

   <!—激活控制反转功能 -->

  <body onload="dwr.engine.setActiveReverseAjax(true);">

   <div style="font-size:200%;" id="Display"></div>

 

  </body>

</html>

其中,我们在body.onLoad时激活控制反转功能,并且在下面添加一个用于显示信息的<div>标签,指定它的ID为Display。

在dwr.xml中配置相应的类ServerTimerTigger.Java,同时需要在web.xml中加入以下申明来开启反转功能:

<init-param>

<param-name>activeReverseAjaxEnabled</param-name>

   <param-value>true</param-value>

</init-param>

然后在JAVA里添加如下的方法:

public static void sendMessageToClient(String str) {

        WebContext wctx = WebContextFactory.get();

        Collection sessions = wctx.getAllScriptSessions();

        Util pages = new Util(sessions);

 

        pages.setValue("Display", str);

}

根据你设置的触发器(可能是一个Servlet)当到达需要通知的时间时,调用上面的方法,将你要通知给所有用户的信息传递给sendMessageToClient方法,这个方法会在所有用户的页面上寻找Id为Display的Element,并将其中的值改变为你传入的参数。

6         FAQ

这里有一些常见的使用DWR的问题答案。

6.1     Object Error

有很多情况会导致Object Error的出现,多数情况为类型不匹配,或者你试图用$()方法查找了一个不存在的组件后对其操作等。

如果在同步调用时并且在代码完全正确的前提下,出现该错误,请检查你的DWR版本是否为DWR 2.0 RC4版。

6.2     TransformerFactoryConfigurationError

这个问题的现象是在启动有DWR的Web应用时出现如下stack trace:

root cause

javax.xml.transform.TransformerFactoryConfigurationError:

  Provider org.apache.xalan.processor.TransformerFactoryImpl not found

       javax.xml.transform.TransformerFactory.newInstance(Unknown Source)

这个问题和DWR没有什么关系,那是因为Tomcat没有配置好。比较简单的解决办法是下载Xalan替换掉$TOMCAT-HOME/common/lib目录下的xalan.jar文件。DWR2.0能更好的处理这个问题,但是本质的问题还是因为DWR的XML序列化需要有XSLT解析器的支持。

如果你用JDK5还是有这个问题的话,你可以增加以下VM参数来使Tomcat正常工作。

-Djavax.xml.transform.TransformerFactory=

   com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl

6.3     XML解析错误

在刚开始用DWR的时候经常遇到的一个错误就是XML解析错误。其实这和DWR没有多大关系,主要是因为Tomcat里面自带的Xerces的问题,要不是该有的时候没有,要不是不该有的时候有了。

JDK 1.3自身没有XML解析器,所以你需要xercesImpl.jar和xml-apis.jar.

JDK 1.4.0 和 JDK 1.4.1 虽然有了XML解析器,但是有很多bug,所以你还是需要把xercesImpl.jar放到tomcat/common/endorsed目录下。

JDK 1.4.2和JDK 5自带的XML解析器工作的很好,你就不需要再加其他的了。

另外要提的一点是,不同版本的Tomcat需要的XML解析器不一样。所以要注意检查它和JDK的版本兼容性。

6.4     用BEA Weblogic的Classpath问题

Weblogic 8.1(有可能其他版本同样)可能找不到DWR的类。

这大多出现在dwr.jar放在APP-INF目录下(APP_INF/lib)的情况。在这种情况下DWR依然可以工作,例如debug页面可以看见,但是DWR找不到你的类。

解决办法是把dwr.jar放到WEB-INF/lib目录下。

6.5     没有cookies的情况下用DWR

当不能用cookies时,servlet规范通过URL重写来支持HttpSession。DWR 2.x通过它生成的URL来支持这项功能。但是DWR 1.x没有这个功能。你可以通过以下办法让DWR 1.x 也支持cookies:

从dwr.jar中提取engine.js,保存到你的文件系统中,就像jsp文件一样.

修改"DWREngine._sendData = function(batch)" 方法, 加入一行:

statsInfo += ";jsessionid=" + <%="'"+session.getId()+"'"%>

这样就可以让DWR 1.x支持url重写了。DWR 2+默认支持。

7         术语解释

7.1     RPC

RPC被称作远程过程调用。是指将网络信息包发送给某个远程主机,它执行起来类似一般的过程调用(或方法调用)。

0
0
  • 上一篇DWR 几种使用方法
  • 下一篇OA系统的技术发展

我的同类文章

  • •DWR 几种使用方法2008-10-30
  • •DWR 与 SPRING 集成配置2008-10-30
猜你在找
JavaWeb程序设计
JavaScript视频教程
JavaScript for Qt Quick(QML)
外行人遁入Java Web入门
PHP面向对象设计模式
查看评论
1楼 jzhang2007 2009-11-24 09:50发表 [回复]
Have you looked at vtd-xml? it is a lot faster and memory efficient than DOM4J and JDOM

http://vtd-xml.sf.net

1 0
原创粉丝点击