教你如何使用Dwr

如何开始用DWR

有两种方法开始DWR,简单的方式是下载WAR文件然后看看。但是这不能帮你知道如何轻松的把DWR整合到你的web应用中,所以还是推荐你按照下面的3个步骤做:

1.安装DWRJar

下载dwr.jar文件。把它放到你的webappWEB-INF/lib目录下。那里可能已经有很多其他的jar文件了。

2.编辑配置文件

需要把下面的代码加到WEB-INF/web.xml文件中。<servlet>那部分需要和其他的<servlet>在一起,<servlet-mapping>部分也一样。

<servlet>

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

<display-name>DWRServlet</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文件。可以从最简单的配置开始:

<!DOCTYPEdwrPUBLIC

"-//GetAheadLimited//DTDDirectWebRemoting1.0//EN"

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

<dwr>

<allow>

<createcreator="new"javascript="JDate">

<paramname="class"value="java.util.Date"/>

</create>

<createcreator="new"javascript="Demo">

<paramname="class"value="your.java.Bean"/>

</create>

</allow>

</dwr>

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

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

·不要出现Javascript保留关键字;和保留关键字同名的函数指定被排除。多数Javascript的关键字和Java是相同的。所以你不可能有一个方法叫做"try()"。但是该死"delete()"对与Javascript有着特殊意义,而对Java则不是。

·Javascript方法重载是不支持的,所以尽量不要再Java中使用。

3.访问下面的URL

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

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

自己动手试一下!

怎么在你的web应用中使用

在文档中有很多例子演示如何动态更改页面中的文字、更新列表、操作表单,还有直接更改table中的内容。每一个都有如何实现的介绍。

另一种方式是看刚才的页面中提供的代码:

http://localhost:8080/\[YOUR-WEBAPP\]/dwr/页面,点击你的类。查看源码,找到执行方法的那几行,把那些文字粘贴到你的HTMLJSP中。

要包括下面这些能产生神奇效果的Javascript文件的链接。

<scriptsrc='/[YOUR-WEBAPP]/dwr/interface/[YOUR-SCRIPT].js'></script>

<scriptsrc='/[YOUR-WEBAPP]/dwr/engine.js'></script>

你也可以把其中/[YOUR-WEBAPP]/替换成你的web页面的相对路径。

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

相对而言Java同步调用,创建与Java代码匹配的Ajax远程调用接口的最大挑战来至与实现Ajax的异步调用特性。

DWR通过引入回调函数来解决这个问题,当结果被返回时,DWR会调用这个函数。

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

当然也可以把回调函数做为第一个参数,但是不建议使用这种方法。因为这种方法在处理自动处理http对象(查看"AlternativeMethod")上会有问题。这个方法主要是为向下兼容而存在的。

简单的回调函数

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

publicclassRemote{

publicStringgetData(intindex){...}

}

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

<scripttype="text/javascript"

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

<scripttype="text/javascript"

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

...

functionhandleGetData(str){

alert(str);

}

Remote.getData(42,handleGetData);

42Java方法getData()的一个参数。

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

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

调用元数据对象(Meta-Data)

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

Remote.getData(42,{

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

});

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

超时和错误处理

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

Remote.getData(42,{

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

timeout:5000,

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

});

查找回调函数

有些情况下我们很难区分各种回调选项(记住,Javascript是不支持函数重载的)。例如:

Remote.method({timeout:3},{errorHandler:somefunc});

这两个参数之一是bean的参数,另一个是元数据对象,但是我们不能清楚的告诉DWR哪个是哪个。为了可以跨浏览器,我们假定null==undefined。所以当前的情况,规则是:

·如果第一个或最后一个是一个函数,那么它就是回调函数,没有元数据对象,并且其他参数都是Java的方法参数。

·另外,如果最后一个参数是一个对象,这个对象中有一个callback成员,并且它是个函数,那么这个对象就是元数据对象,其他的都是Java方法参数。

·另外,如果第一个参数是null,我们就假设没有回调函数,并且其他的都是Java方法参数。尽管如此,我们会检查最后一个参数是不是null,如果是就发出警告。

·最后如果最后一个参数是null,那么就没有callback函数。

·另外,发出错误信号是个糟糕的请求格式。

创造一个与Java对象匹配的Javascript对象

假设你有这样的Java方法:

publicclassRemote{

publicvoidsetPerson(Personp){

this.person=p;

}

}

Person对象的结构是这样的:

publicPerson{

privateStringname;

privateintage;

privateDate[]appointments;

//gettersandsetters...

}

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

varp={

name:"FredBloggs",

age:42,

appointments:[newDate(),newDate("1Jan2008")]

};

Remote.setPerson(p);

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

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

TransformerFactoryConfigurationError

这个问题的现象是在启动有DWRWeb应用时出现如下stacktrace:

rootcause

javax.xml.transform.TransformerFactoryConfigurationError:

Providerorg.apache.xalan.processor.TransformerFactoryImplnotfound

javax.xml.transform.TransformerFactory.newInstance(UnknownSource)

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

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

-Djavax.xml.transform.TransformerFactory=

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

XML解析错误

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

·JDK1.3自身没有XML解析器,所以你需要xercesImpl.jarxml-apis.jar.

·JDK1.4.0JDK1.4.1虽然有了XML解析器,但是有很多bug,所以你还是需要把xercesImpl.jar放到tomcat\common\endorsed目录下。

·JDK1.4.2JDK5自带的XML解析器工作的很好,你就不需要再加其他的了。
另外要提的一点是,不同版本的Tomcat需要的XML解析器不一样。所以要注意检查它和JDK的版本兼容性。

BEAWeblogicClasspath问题

Weblogic8.1(有可能其他版本同样)可能找不到DWR的类。

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

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

没有cookies的情况下用DWR

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

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

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

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

这样就可以让DWR1.x支持url重写了。DWR2+默认支持。

传递额外的数据到callback函数

通常我们需要传递额外的数据到callback函数,但是因为所有的回调函数都只有一个参数(远程方法的返回结果),这就需要一些小技巧了。

解决方案就是使用Javascript闭包特性。

例如,你的回调函数原本需要像这个样子:

functioncallbackFunc(dataFromServer,dataFromBrowser){

//用dataFromServer和dataFromBrowser做些事情......

}

那么你可以像这个组织你的函数:

vardataFromBrowser=...;

//定义一个闭包函数来存储dataFromBrowser的引用,并调用dataFromServer

varcallbackProxy=function(dataFromServer){

callbackFunc(dataFromServer,dataFromBrowser);

};

varcallMetaData={callback:callbackProxy};

Remote.method(params,callMetaData);

(调用元数据在脚本介绍中有解释)

换句话说,现在你作为callback函数传递过来的不是一个真正的callback,他只是一个做为代理的闭包,用来传递客户端的数据。

你可以用更简介的形式:

vardataFromBrowser=...;

Remote.method(params,{

callback:function(dataFromServer){

callbackFunc(dataFromServer,dataFromBrowser);

}

});

服务器性能优化

CPU瓶颈:经过严格的测试DWR的性能没什么问题。DWR上性能消耗同web服务器和网络比起来可以忽略不计。如果你真的需要提升DWR的性能的话,可以把log级别设置ERRORFATAL,但是主要还是要看你的编码情况。

Network瓶颈:DWR没有管理你的浏览器缓存的功能,所以它会不断的重复读取DWRjavascript文件。这里有一个简单的解决办法,把javascript文件复制到你的web-app中,这样web服务器就可以更好的利用它了。你也可以考虑把所有的javascript文件合并成一个文件,然后用DOJO的压缩程序处理一个来节省流量。

我们可以做一个补丁,让DWRweb-app启动的时候用时间做为javascript文件的时间戳,但是这个并不十分重要,因为上面的补丁太简单了而且可以压缩合并Javascript文件。

WEB-INF/web.xml参考手册

web.xml中最简单的配置就是简单加入DWRservlet,没有这个配置DWR就不会起作用:

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

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

Logging

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

Commons-Logging

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

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

HttpServlet.log()

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

<init-param>

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

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

</init-param>

可用的值有:FATAL,ERROR,WARN(默认),INFODEBUG

多个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规范的J2EEURL安全机制来给不同的用户不同的访问权限。你只需要简单的定义多个dwrservlet,并且制定不同的名字,url和访问权限。

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

<init-param>

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

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

<description>Whatconfigfiledoweuse?</description>

</init-param>

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

一个使用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>

使用插件(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中。

使用debug/test模式

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

<init-param>

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

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

</init-param>

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

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

DWR就是照上面的样子做的,没有任何保证,所以你的网站的安全是你的责任。请小心。

配置DWR-dwr.xml

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

DTD

这里还有一个dwr.xml对应的DTD文档以及一个用DTDDoc生成的参考手册

创建dwr.xml文件

dwr.xml文件的结构如下:

<!DOCTYPEdwrPUBLIC

"-//GetAheadLimited//DTDDirectWebRemoting1.0//EN"

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

<dwr>

<!--initisonlyneededifyouareextendingDWR-->

<init>

<creatorid="..."class="..."/>

<converterid="..."class="..."/>

</init>

<!--withoutallow,DWRisn'tallowedtodoanything-->

<allow>

<createcreator="..."javascript="..."/>

<convertconverter="..."match="..."/>

</allow>

<!--youmayneedtotellDWRaboutmethodsignatures-->

<signatures>

...

</signatures>

</dwr>

术语

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

<allow>

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

Creators

我们要调用的每个类都需要一个<create...>定义。creator有几种。比较通用的是new关键字和Spring。更多的信息可以参见[Creaters]文档。

Converters

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

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

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

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

·java.lang.String

·java.util.DateSQL中的Date

·以上类型组成的数组

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

·DOM,XOM,JDOMDOM4J中的DOM对象(类似ElementDocument)

要了解如何转换你的JavaBean或者其他类型的参数请查看Converters文档。

<init>

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

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

<signatures>

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

多个dwr.xml文件

可以有多个dwr.xml文件(详细信息见web.xml文档)。每个文件中的定义会被加在一起。DWR用这个功能来加载基础配置文件。我们可以看看标准被配置文件来了解dwr.xml的内容。

转换器

转换器在客户端和服务器之间转换数据.

下面这些转换器有单独章节介绍

·ArrayConverter

·BeanandObjectConverters

·CollectionConverter

·EnumConverter

·DOMObjects

·Hibernate整合

·ServletObjects(HttpServletRequest,HttpSession,etc)

基础的转换器

原生类型,String,像BigDecimal这样的简单对象的转换器已经有了。你不需要在dwr.xml<allow>部分的<convert>中定义。它们默认支持。

默认支持的类型包括:boolean,byte,short,int,long,float,double,char,java.lang.Boolean,java.lang.Byte,java.lang.Short,java.lang.Integer,java.lang.Long,java.lang.Float,java.lang.Double,java.lang.Character,java.math.BigInteger,java.math.BigDecimaljava.lang.String

Date转换器

Date转换器负责在JavascriptDate类型与Java中的Date类型(java.util.Date,java.sql.Date,java.sql.Timesorjava.sql.Timestamp)之间进行转换。同基础的转换器一样,DateConverter默认是支持的。

如果你有一个Javascript的字符串(例如"01Jan2010"),你想把它转换成JavaDate类型有两个办法:在javascript中用Date.parse()把它解析成Date类型,然后用DWRDateConverter传递给服务器;或者把它作为字符串传递给Server,再用Java中的SimpleDateFormat(或者类似的)来解析。

同样,如果你有个JavaDate类型并且希望在HTML使用它。你可以先用SimpleDateFormat把它转换成字符串再使用。也可以直接传DateJavascript,然后用Javascript格式化。第一种方式简单一些,尽管浪费了你的转换器,而且这样做也会是浏览器上的显示逻辑受到限制。其实后面的方法更好,也有一些工具可以帮你,例如:

·TheJavascriptToolboxDateformatter

·WebDevelopersNotesonDateformatting

其他对象

其实创建自己的转换器也很简单。Converter接口的Javadoc包含了信息。其实这种需要很少出现。在你写自己的Converter之前先看看BeanConverter,它有可能就是你要的。

TheCreators–创造器

dwr.xml文件中的create元素的结构如下:

<allow>

<createcreator="..."javascript="..."scope="...">

<paramname="..."value="..."/>

<authmethod="..."role="..."/>

<excludemethod="..."/>

<includemethod="..."/>

</create>

...

</allow>

这里的多数元素都是可选的-你真正必须知道的是指定一个creator和一个javascript名字。

creator属性是必须的-它用来指定使用那种创造器。

默认情况下DWR1.18种创造器。它们是:

·new:Javanew关键字创造对象。

·none:它不创建对象,看下面的原因。(v1.1+)

·scripted:通过BSF使用脚本语言创建对象,例如BeanShellGroovy

·spring:通过Spring框架访问Bean

·jsf:使用JSFBean(v1.1+)

·struts:使用StrutsFormBean(v1.1+)

·pageflow:访问BeehiveWeblogicPageFlow(v1.1+)

如果你需要写自己的创造器,你必须在init部分注册它。

javascript属性用于指定浏览器中这个被创造出来的对象的名字。你不能使用Javascript的关键字。

scope属性非常类似servlet规范中的scope。它允许你指定这个bean在什么生命范围。选项有"application","session","request""page"。这些值对于ServletJSP开发者来说应该相当熟悉了。

scope属性是可选的。默认是"page"。如果要使用"session"需要cookies。当前的DWR不支持ULR重写。

param元素被用来指定创造器的其他参数,每种构造器各有不同。例如,"new"创造器需要知道要创建的对象类型是什么。每一个创造器的参数在各自的文档中能找到。请查看上面的链接。

includeexclude元素允许创造器来限制类中方法的访问。一个创造器必须指定include列表或exclude列表之一。如果是include列表则暗示默认的访问策略是"拒绝";如果是exclude列表则暗示默认的访问策略是"允许"

例如要拒绝防范除了setWibble()以外的所有方法,你应该把如下内容添加到dwr.xml中。

<createcreator="new"javascript="Fred">

<paramname="class"value="com.example.Fred"/>

<includemethod="setWibble"/>

</create>

对于加入到create元素中的类的所有方法都是默认可见的。

auth元素允许你指定一个J2EE的角色作为将来的访问控制检查:

<createcreator="new"javascript="Fred">

<paramname="class"value="com.example.Fred"/>

<authmethod="setWibble"role="admin"/>

</create>

'none'创造器

'none'创造器不创建任何对象-它会假设你不需要创建对象。这有可能是对的,有两个原因。

你可能在使用的scope不是"page"(看上面),并在在前面已经把这个对象创建到这个scope中了,这时你就不需要再创建对象了。

还有一种情况是要调用的方法是静态的,这时也不需要创建对象。DWR会在调用创建器之前先检查一下这个方法是不是静态的。

对于上诉两种情况,你仍然需要class参数,用来告诉DWR它是在操作的对象类型是什么。

使用静态方法

DWR会在调用创建器之前先检查一下这个方法是不是静态的,如果是那么创造器不会被调用。很显然这个逻辑适用于所有创造器,尽管如此"null"创造器是最容易配置的。

适用单例类

对于单例类的创建,最好适用BeanShellBSF来实例化对象。详细信息参见'Scripted'创造器

其他创造器

我么偶尔也需要一些新的创造器,最常见的是一个EjbCreator。讨论新的创造器的好地方是在邮件列表

DWRHttpSessionBindingListeners

DWR1.x中存贮已经创造的Bean的方法需要注意,它在每次请求时都会调用相同的setAttribute()方法。就是说,如果一个Beandwr.xml中的声明周期设置为session,再每次调用bean中的方法时,DWR都会执行一次session.setAttribute(yourBean)。这看上去没有什么危害,但是如果你要使用servlet的事件机制的,就是说用了HttpSessionBindingListener接口,你就会发现valueBoundvalueUnbound事件在每次调用时都会发生,而不是你想像的在bean被创建时以及session过期时。

DWR2只在第一次创建对象时调用setAttribute()

dwr.xml中的签名(Signatures)

signatures段使DWR能确定集合中存放的数据类型。例如下面的定义中我们无法知道list中存放的是什么类型。

publicclassCheck

{

publicvoidsetLotteryResults(Listnos)

{

...

}

}

signatures段允许我们暗示DWR应该用什么类型去处理。格式对以了解JDK5的泛型的人来说很容易理解。

<signatures>

<![CDATA[

importjava.util.List;

importcom.example.Check;

Check.setLotteryResults(List<Integer>nos);

]]>

</signatures>

DWR中又一个解析器专门来做这件事,所以即便你的环境时JDK1.3DWR也能正常工作。

解析规则基本上会和你预想规则的一样(有两个例外),所以java.lang下面的类型会被默认import

第一个是DWR1.0中解析器的bug,某些环境下不能返回正确类型。所以你也不用管它了。

第二个是这个解析器时"阳光(sunnyday)"解析器。就是说它非常宽松,不想编译器那样严格的保证你一定正确。所以有时它也会允许你丢失import

<signatures>

<![CDATA[

importjava.util.List;

Check.setLotteryResults(List<Integer>);

]]>

</signatures>

将来的DWR版本会使用一个更正式的解析器,这个编译器会基于官方Java定义,所以你最好不要使用太多这个不严格的东西。

signatures段只是用来确定泛型参数中的类型参数。DWR会自己使用反射机制或者运行时类型确定类型,或者假设它是一个String类型。所以:

不需要signatures-没有泛型参数:

publicvoidmethod(Stringp);

publicvoidmethod(String[]p);

需要signatures-DWR不能通过反射确定:

publicvoidmethod(List<Date>p);

publicvoidmethod(Map<String,WibbleBean>p);

不需要signatures-DWR能正确的猜出:

publicvoidmethod(List<String>p);

publicvoidmethod(Map<String,String>p);

不需要signatures-DWR可以通过运行时类型确定:

publicList<Date>method(Stringp);

没有必要让Javascript中的所有对象的key都是String类型-你可以使用其他类型作为key。但是他们在使用之前会被转换成String类型。DWR1.xJavascript的特性把key转换成StringDWR2.0可能会用toString()方法,在服务段进行这一转换。

engine.jsFunctions

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

Theengine.jsfile

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

<scripttype=&apos;text/javascript&apos;

src=&apos;/[YOUR-WEB-APP]/dwr/engine.js&apos;>

</script>

使用选项

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

DWREngine.setTimeout(1000);

或者在单次调用级别上(假设RemoteDWR暴露出来了)

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做为超时的时间。

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

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

如果以上叙述让你感到混乱,不用担心。DWR的的设计往往和你想象中的一样,所以其实这些并不复杂。

选项索引

下面是可用选项列表。

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页面时的动作(通常表示超时)

调用处理器(CallHandler)(注册到单独调用上的,而不是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调用

废弃的选项

Option

Global

Batch

Call

Summary

verb

1.0

1.1

1.1

2.0废弃。使用&apos;httpMethod&apos;代替

method

1.0

1.1

1.1

2.0废弃。使用&apos;rpcType&apos;代替

将来的

Option

Global

Batch

Call

Summary

onBackButton

2.1?

2.1?

-

用户按了back按钮后的动作

onForwardButton

2.1?

2.1?

-

用户按了forward按钮的动作

保证的责任

DWR的目的是让你确切的知道所有调用的动作。知道了浏览器存在的bug,这是可以做到了。

如果你设置了callback,exceptionHandler,errorHandler,warningHandlertextHtmlHandlerDWR就应该总是为每一个请求提供响应。

CallBatching

你可以使用batch来批量的执行远程调用。这样可以减少与服务器的交互次数,所以可以提交反应速度。

一个batchDWREngine.beginBatch()开始,并以DWREngine.endBatch()结束。当DWREngine.endBatch()被调用,我们就结束了远程调用的分组,这样DWR就在一次与服务器的交互中执行它们。

DWR会小心的处理保证所有的回调函数都会被调用,所以你可以明显的打开和关闭批处理。只要别忘了调用endBatch(),否则所有的远程调用永远的处于列队中。

警告

很明显,把一些远程调用放在一起执行也会产生一些影响。例如不能在batch里面执行同步调用。

所有的元数据选项,例如hooks,timeoutserrorHandlers都在batch级别的,而不是单次调用级别上的。所以如果一个batch中有两个调用设置了不同的超时,除了最后一个其他的都被忽略。

顺序调用

因为Ajax一般是异步调用,所以远程调用不会按照发送的顺序返回。DWREngine.setOrdered(boolean)允许结果严格按照发送的顺序返回。DWR在旧的请求安全返回以后才去发送新的请求。

我们一定需要保证请求按照发送的顺序返回吗?(默认为false

警告:把这个设置为true会减慢你的应用程序,如果一个消息丢失,浏览器就会没有响应。很多时候即使用异步调用也有更好的解决办法,所以在用这一功能之前先好好考虑一下。

处理错误和警告

当因为一些原因调用失败,DWR就会调用错误和警告handler(根据错误的激烈程度),并传递错误消息。

你可以用这种方法来在alert窗口或状态来中显示错误信息。

你可以使用DWREngine.setErrorHandler(function)来改变错误处理方式,同样通过DWREngine.setWarningHandler(function)来改变警告处理方式。

更多关于处理错误和警告,请查看错误处理页面

设置超时

DWREngine.setTimeout(),单次调用和批量调用级别的元数据选项,允许你设置一个超时值。全局的DWREngine.setTimeout()函数设置全局超时。如果设置值为0(默认)可以将超时关掉。

setTimeout()的单位是毫秒。如果调用超时发生,错误处理器就会被调用。

一个例子:

Remote.method(params,{

callback:function(data){alert("itworked");},

errorHandler:function(message){alert("itbroke");},

timeout:1000

});

如果Remote.method()调用超过了1分钟还没有返回,"itbroke"消息就会被显示。

远程调用Hooks

DWREngine.setPreHook(function)DWREngine.setPostHook(function)

如果你想在DWR调用之前出现一个提示,你可以设置pre-hook函数。它将会被调用,但是没有参数传递到这个函数。当你希望让一些按钮在调用期间变灰来防止被再次使用,这一功能将会很有用。

post-hook用来和pre-hook一起使用来逆转pre-hook产生的做的一些改变。

一个使用preposthook的例子就是DWRUtil.useLoadingMessage()函数。

远程调用选项

DWR有一些选项用来控制远程调用的处理方式。methodverb对于用户应该时透明的,但是不同的浏览器效果的可能不一样。一般来说DWR会选择正确方法来处理,但是这些选项对于在不同效果的浏览器之间开发很有用。

DWREngine.setAsync(flag)

DWR1.0不支持。

我们指定让XHR异步调用了吗?默认为true。警告如果你使用的时IFrame或者ScriptTag这一选项被忽略。一般来说把他变成false是个糟糕的做法。因为那样会使你的浏览器变慢。

要设置全局同步机制:

DWREngine.setAsync(true);

或者设置单次调用同步:

Remote.method(params,{

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

async:true

});

或者在batch里面:

DWREngine.beginBatch();

Remote.method1(params,callback1);

Remote.method2(params,callback2);

DWREngine.endBatch({

async:true

});

DWREngine.setMethod(newmethod)

用来设置恰当的方法。setMethod()不能把正使用你选择的方法,它只是保证首先尝试使用那个方法。newmethod必须是DWREngine.XMLHttpRequest或者DWREngine.IFrame,或者2.0以后的DWREngine.ScriptTag

XMLHttpRequest时默认的,并且大多情况下可用。当ActiveX禁用IFrame就有用了,尽管DWR能自动检测出这种情况并切换到IFrame。当你要突破跨域调用的限制,ScriptTag就很有用了。

例如,要设置全局的远程调用方法:

DWREngine.setMethod(DWREngine.IFrame);

或者单次调用:

Remote.method(params,{

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

method:DWREngine.IFrame

});

或者批量调用:

DWREngine.beginBatch();

Remote.method1(params,callback1);

Remote.method2(params,callback2);

DWREngine.endBatch({

method:DWREngine.IFrame

});

DWREngine.setVerb(verb)

这个选项允许你选择POSTGET,无论时用iframe还是XMLHttpRequest方法。一些浏览器(例如,旧版的Safari)不支持XHR-POST所以DWR就自动切换到GET,即使你设置POSTverb。所以setVerb()应当被仅仅做为一个堤示。

如果使用ScriptTag来远程调用,设置verb时没有的。

例如,设置全局远程调用的verb

DWREngine.setVerb("GET");

设置单次调用:

Remote.method(params,{

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

verb:"GET"

});

设置批量调用:

DWREngine.beginBatch();

Remote.method1(params,callback1);

Remote.method2(params,callback2);

DWREngine.endBatch({

verb:"GET"

});

util.js

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

你可以在DWR以外使用它,因为它不依赖于DWR的其他部分。你可以下载整个DWR或者单独下载.

4个基本的操作页面的函数:getValue[s]()setValue[s]()可以操作大部分HTML元素除了tablelistimagegetText()可以操作selectlist

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

还有一些其他功能不是DWRUtil的一部分。但它们也很有用,它们可以用来解决一些小问题,但是它们不是对于所有任都通用的。

$()

$()函数(它是合法的Javascript名字)是从Protoype偷来的主意。大略上的讲:$=document.getElementById。因为在Ajax程序中,你会需要写很多这样的语句,所以使用$()会更简洁。

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

可以看看DWRUtil.toDescriptiveString的演示。

从技术角度来讲他在IE5.0中是不能使用的,因为它使用了Array.push,尽管如此通常它只是用来同engine.js一起工作。如果你不想要engine.js并且在IE5.0中使用,那么你最好为Array.push找个替代品。

util.js:生成列表

DWR的一个常遇到的任务就是根据选项填充选择列表。下面的例子就是根据输入填充列表。

下面将介绍DWRUtil.addOptions()的几种是用方法。

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

varsel=DWRUtil.getValue(id);

DWRUtil.removeAllOptions(id);

DWRUtil.addOptions(id,...);

DWRUtil.setValue(id,sel);

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

DWRUtil.addOptions(id,\["Pleaseselect..."]);

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

DWRUtil.addOptions5种模式

数组:DWRUtil.addOptions(selectid,array)会创建一堆option,每个option的文字和值都是数组元素中的值。

对象数组(指定text):DWRUtil.addOptions(selectid,data,prop)用每个数组元素创造一个optionoption的值和文字都是在prop中指定的对象的属性。

对象数组(指定textvalue):DWRUtil.addOptions(selectid,array,valueprop,textprop)用每个数组元素创造一个optionoption的值是对象的valueprop属性,option的文字是对象的textprop属性。

对象:DWRUtil.addOptions(selectid,map,reverse)用每个属性创建一个option。对象属性名用来作为option的值,对象属性值用来作为属性的文字,这听上去有些不对。但是事实上却是正确的方式。如果reverse参数被设置为true,那么对象属性值用来作为选项的值。

对象的Map:DWRUtil.addOptions(selectid,map,valueprop,textprop)map中的每一个对象创建一个option。用对象的valueprop属性做为optionvalue,用对象的textprop属性做为option的文字。

olul列表:DWRUtil.addOptions(ulid,array)用数组中的元素创建一堆li元素,他们的innerHTML是数组元素中的值。这种模式可以用来创建ulol列表。

util.js:生成Table

DWR通过这两个函数来帮你操作tableDWRUtil.addRows()DWRUtil.removeAllRows()。这个函数的第一个参数都是tabletbodytheadtfootid。一般来说最好使用tbody,因为这样可以保持你的headerfooter行不变,并且可以防止InternetExplorerbug

DWRUtil.removeAllRows()

语法:

DWRUtil.removeAllRows(id);

描述:
通过id删除table中所有行。

参数:

·idtable元素的id(最好是tbody元素的id)

DWRUtil.addRows()

语法:

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

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

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

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

foreachmemberinarray

foreachfunctionincellfuncs

createcellfromcellfunc(array[i])

参数:

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

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

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

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

选项包括:

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

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

DWRUtil.getText(id)

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

DWRUtil.getValue(id)

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

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

DWRUtil.getValues()

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

基于FormgetValues()

DWR1.1开始getValues()可以传入一个HTML元素(一个DOM对象或者id字符串),然后从它生成一个reply对象。

DWRUtil.onReturn

当按下return键时,得到通知。

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

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

<inputtype="text"οnkeypress="DWRUtil.onReturn(event,submitFunction)"/>

<inputtype="button"οnclick="submitFunction()"/>

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

一般来说DWR不是一个Javascript类库,所以它应该试图满足这个需求。不管怎样,这是在使用Ajax过程中一个很有用函数。

onSubmit

这个函数的工作原理是onSubmit()事件只存在于<FORM...>元素上。

DWRUtil.selectRange

选择一个输入框中的一定范围的文字。
你可能为了实现类似"Googlesuggest"类型的功能而需要选择输入框中的一定范围的文字,但是不同浏览器间选择的模型不一样。这DWRUtil函数可以帮你实现。

DWRUtil.setValue(id,value)

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

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

DWRUtil.setValues()

setValues()setValue()非常相似,除了输入的是包含name/value对的javascript对象。nameHTML元素的IDvalue是你想要设置给相应的元素的值。

DWRUtil.toDescriptiveString

DWRUtil.toDescriptiveString()函数比默认的toString()更好。第一个参数是要调试的对象,第二个参数是可选的,用来指定内容深入的层次:

·0:单行调试

·1:多行调试,但不深入到子对象。

·2:多行调试,深入到第二层子对象
以此类推。一般调试到第二级是最佳的。

还有第三个参数,定义初始缩进。这个函数不应该被用于调式程序之外,因为以后可能会有变化。

DWRUtil.useLoadingMessage

设置一个Gmail风格的加载信息。所有演示页面(dynamictext,selectionlists,livetables,liveforms,dynamicvalidationaddressentry)都使用了GMail风格的加载消息。

这个方法将来可能被废弃,因为这个实现实在太专断了。为什么是红色,为什么在右上角,等等。唯一的真正答案就是:抄袭GMail。这里的建议是以本页面中的代码为模板,根据你的需求自定义。

你必须在页面加载以后调用这个方法(例如,不要在onload()事件触发之前调用),因为它要创建一个隐藏的div来容纳消息。

最简单的做法时在onload事件中调用DWRUtil.useLoadingMessage,像这样:

<head>

<script>

functioninit(){

DWRUtil.useLoadingMessage();

}

</script>

...

</head>

<bodyοnlοad="init();">

...

可能有些情况下你是不能容易的编辑headerbody标签(如果你在使用CMS,这很正常),在这样的情况下你可以这样做:

<script>

functioninit(){

DWRUtil.useLoadingMessage();

}

if(window.addEventListener){

window.addEventListener("load",init,false);

}

elseif(window.attachEvent){

window.attachEvent("onload",init);

}

else{

window.οnlοad=init;

}

</script>

下面这些是这个函数的代码,它对于你要实现自己的加载消息很有用。这个函数的主要内容是动态创建一个div(iddisabledZone)来容纳消息。重要的代码是当远程调用时使它显示和隐藏:

DWREngine.setPreHook(function(){

$('disabledZone').style.visibility='visible';

});

DWREngine.setPostHook(function(){

$('disabledZone').style.visibility='hidden';

});

Thisisfairlysimpleandmakesitquiteeasytoimplementyourown"loading"message.

functionuseLoadingMessage(message){

varloadingMessage;

if(message)loadingMessage=message;

elseloadingMessage="Loading";

DWREngine.setPreHook(function(){

vardisabledZone=$('disabledZone');

if(!disabledZone){

disabledZone=document.createElement('div');

disabledZone.setAttribute('id','disabledZone');

disabledZone.style.position="absolute";

disabledZone.style.zIndex="1000";

disabledZone.style.left="0px";

disabledZone.style.top="0px";

disabledZone.style.width="100%";

disabledZone.style.height="100%";

document.body.appendChild(disabledZone);

varmessageZone=document.createElement('div');

messageZone.setAttribute('id','messageZone');

messageZone.style.position="absolute";

messageZone.style.top="0px";

messageZone.style.right="0px";

messageZone.style.background="red";

messageZone.style.color="white";

messageZone.style.fontFamily="Arial,Helvetica,sans-serif";

messageZone.style.padding="4px";

disabledZone.appendChild(messageZone);

vartext=document.createTextNode(loadingMessage);

messageZone.appendChild(text);

}

else{

$('messageZone').innerHTML=loadingMessage;

disabledZone.style.visibility='visible';

}

});

DWREngine.setPostHook(function(){

$('disabledZone').style.visibility='hidden';

});

}

下面的做法能简单的使用有加载消息图片:

functionuseLoadingImage(imageSrc){

varloadingImage;

if(imageSrc)loadingImage=imageSrc;

elseloadingImage="ajax-loader.gif";

DWREngine.setPreHook(function(){

vardisabledImageZone=$('disabledImageZone');

if(!disabledImageZone){

disabledImageZone=document.createElement('div');

disabledImageZone.setAttribute('id','disabledImageZone');

disabledImageZone.style.position="absolute";

disabledImageZone.style.zIndex="1000";

disabledImageZone.style.left="0px";

disabledImageZone.style.top="0px";

disabledImageZone.style.width="100%";

disabledImageZone.style.height="100%";

varimageZone=document.createElement('img');

imageZone.setAttribute('id','imageZone');

imageZone.setAttribute('src',imageSrc);

imageZone.style.position="absolute";

imageZone.style.top="0px";

imageZone.style.right="0px";

disabledImageZone.appendChild(imageZone);

document.body.appendChild(disabledImageZone);

}

else{

$('imageZone').src=imageSrc;

disabledImageZone.style.visibility='visible';

}

});

DWREngine.setPostHook(function(){

$('disabledImageZone').style.visibility='hidden';

});

}

然后你就可以这样使用:useLoadingImage("images/loader.gif");

h1util.js中的功能
这里有一些功能不适合加入到DWRUtil中。它们在解决一下特殊问题是很有用,但是他们还不够通用以适用任何场合。

修补浏览器事件

如果你创建了一个DOM元素,然后用addAttribute在这个元素上创建了一个事件,那么他们不能被正常的触发。你可以使用下面的脚本来遍历一个DOM树,并重新为他们绑定事件,这样他们就能正常的触发了。

'click'改成你希望的事件。

DWREngine._fixExplorerEvents=function(obj){

for(vari=0;i<obj.childNodes.length;i++){

varchildObj=obj.childNodes[i];

if(childObj.nodeValue==null){

varonclickHandler=childObj.getAttribute('onclick');

if(onclickHandler!=null){

childObj.removeAttribute('onclick');

//Ifusingprototype:

//Event.observe(childObj,'click',newFunction(onclickHandler));

//Otherwise(butwatchoutformemoryleaks):

if(element.attachEvent){

element.attachEvent("onclick",onclickHandler);

}

else{

element.addEventListener("click",onclickHandler,useCapture);

}

}

DWREngine._fixExplorerEvents(childObj);

}

}

传递额外的数据到callback函数

通常我们需要传递额外的数据到callback函数,但是因为所有的回调函数都只有一个参数(远程方法的返回结果),这就需要一些小技巧了。

解决方案就是使用Javascript闭包特性。

例如,你的回调函数原本需要像这个样子:

functioncallbackFunc(dataFromServer,dataFromBrowser){

//用dataFromServer和dataFromBrowser做些事情......

}

那么你可以像这个组织你的函数:

vardataFromBrowser=...;

//定义一个闭包函数来存储dataFromBrowser的引用,并调用dataFromServer

varcallbackProxy=function(dataFromServer){

callbackFunc(dataFromServer,dataFromBrowser);

};

varcallMetaData={callback:callbackProxy};

Remote.method(params,callMetaData);

(调用元数据在脚本介绍中有解释)

换句话说,现在你作为callback函数传递过来的不是一个真正的callback,他只是一个做为代理的闭包,用来传递客户端的数据。

你可以用更简介的形式:

vardataFromBrowser=...;

Remote.method(params,{

callback:function(dataFromServer){

callbackFunc(dataFromServer,dataFromBrowser);

}

});

错误处理

1.0版中错误处理规则有些bug1.1修复了这些错误。

DWR中有一些全局的处理器(一个错误相关的,叫做errorHandler,另一个警告相关的,叫做warningHandler)DWR会默认指定一些全局处理器。你可以这样的改变全局级别的处理器:

DWREngine.setErrorHandler(handler);

你也可以指定单次调用和批量调用的错误和警告处理。例如,在调用元数据中:

Remote.method(params,{

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

errorHandler:function(errorString,exception){...}

});

或者,在批量元数据中:

DWREngine.beginBatch();

Remote.method(params,function(data){...});

//其他的远程调用

DWREngine.endBatch({

errorHandler:function(errorString,exception){...}

});

异常

DWR可以转换异常,这样他们会变成Javascript中的错误(他们可以被抛出,因为这可能在异步调用中发生)
例如,如果我们远程调用下面的Java类:

publicclassRemote{

publicStringgetData(){

thrownewNullPointerException("message");

}

}

那么在Javascript中我们加入下面这些:

functioneh(msg){

alert(msg);

}

{

DWREngine.setErrorHandler(eh);

Remote.getData(function(data){alert(data);});

结果会通过eh()错误处理器调用alert窗口的,显示消息例如调用异常的getMessage()得到的消息。

找出更多的信息

我们可以把整个异常传地到Javascript中。如果在dwr.xml中加入转换异常本身的能力:

<convertconverter="bean"match="my.special.FunkyException"/>

在这里例子中FunkyException被指定,因为它不仅仅包括一个消息,它还包括一些关于异常的额外数据。例如,SQLException包含错误号,SAX异常包含错误的行和列等等。所以我们可以把上面的例如改为:

publicclassRemote{

publicStringgetData(){

Datewhen=newDate();

thrownewFunkyException("message",when);

//FunkyException有一个getWhen()方法

}

}

然后在Javascript中是这样的:

functioneh(msg,ex){

alert(msg+",date="+ex.when);

}

DWREngine.setErrorHandler(eh);

Remote.getData(function(data){alert(data);});

结果会是一个eh()错误处理器调用的alert框,上面有这些信息:"message,date=MonJan01200810:00:00GMT+0100"

被传递到错误处理器的ex对象会包含异常在服务端的所有属性,但是异常栈信息没有。

DWRSpring

DWRSpring一起工作的步骤

1.确认你用的是最新版的DWRSpring创造器已经有了变化,所以你最好检查一下DWR最新版本

2.确认你查看过开始指南中的内容。

3.确认你的SpringBeanDWR外面运行良好。

4.配置DWRSpring一起工作。(看下面)

5.查看演示页面:http://localhost:8080/[YOUR-WEBAPP]/dwr,检查springBean是否出现。

DWR对于Spring没有运行期依赖,所以如果你不使用Spring那么Spring的支持不会产生任何影响到。

TheSpringCreator

这个创造器会查找spring的中配置的Bean,用Spring去创建它们。如果你已经在使用Spring,那么这个创造器会非常有用。

你可以通过下面的方式来创建远程调用的Bean

<allow>

...

<createcreator="spring"javascript="Fred">

<paramname="beanName"value="Shiela"/>

</create>

</allow>

寻找你的Spring配置

有三种方式寻找配置文件:

ContextLoaderListener

最简单的方式是使用org.springframework.web.context.ContextLoaderListener。你不必使用所有的Spring-MVC功能,只需要这个Listener就够了,所以这是个不错的方案。你需要在WEB-INF/web.xml中做如下配置:

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/classes/beans.xml</param-value>

</context-param>

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>

</listener>

我能找到的ContextLoaderListener的最好的文档就是javadoc。如果你知道还有更好的文档,请告知我。

RobSanheim指出还有一个能深入了解ContextLoaderListener的文档。

使用location*参数

如果你要在dwr.xml中指定使用哪些bean,你可以使用location*参数。你可以指定任意多个文件,只要参数以location开始并且唯一即可。例如:location-1,location-2。这些location被用做SpringClassPathXmlApplicationContext的参数:

<allow>

...

<createcreator="spring"javascript="Fred">

<paramname="beanName"value="Shiela"/>

<paramname="location"value="beans.xml"/>

</create>

</allow>

直接设置BeanFactory

SpringCreator有一个静态方法setOverrideBeanFactory(BeanFactory)用来通过编程的方式直接设置BeanFactory

配置DWRSpring

BramSmeets写了一个有意思的blog,教你配置DWR使用beans.xml代替WEB-INF/web.xml

我也对于如何在beans.xml中指定dwr.xml很感兴趣,尽管这看上去有些Spring传染病的感觉。有人知道如何实现吗?请加入邮件列表并告诉大家。

通过DWR来调用WebWorkAction

WebWork支持在DWR2.0m3以后才有。

要可以通过DWR调用WWAction,要做两件事。

步骤1:配置dwr.xml

你必须在dwr的配置文件中加入这样的配置:

<createcreator="none"javascript="DWRAction">

<paramname="class"value="org.directwebremoting.webwork.DWRAction"/>

<includemethod="execute"/>

</create>

<convertconverter="bean"match="org.directwebremoting.webwork.ActionDefinition">

<paramname="include"value="namespace,action,method,executeResult"/>

</convert>

<convertconverter="bean"match="org.directwebremoting.webwork.AjaxResult"/>

这样你AjaxWebWorkAction调用返回一个action实例(而不是文字)。然后你必须包括action对象的转换器定义(package级别或单独action)

<convertconverter="bean"match="<your_action_package>.*"/>

步骤2:JSP中导入脚本

下面这些代码开启DWR调用Action的功能。你还要导入DWRActionUtil.js脚本(在你的web脚本路径中)

使用
像这样在JS中调用Action

DWRActionUtil.execute(id,params,callback[,displayMessage]);

id参数可以是下面这些:

·actionUri:要调用actionURI(没有.action).例如:

DWRActionUtil.execute('/ajax/TestFM','myform','doOnTextResult');

·actionDefinitionObject:xwork.xml中定义的action对象.必须指定下面的内容:

onamespace:xwork.xmlaction的名称空间

oaction:xwork.xmlaction的名字

oexecuteResult:true|false(是否执行action的结果,如果false直接返回action实例)
例如:

oDWRActionUtil.execute({

onamespace:'/ajax',

oaction:'TestJS',

oexecuteResult:'true'

},'data',doOnJSResult,"stream...");

params必须是这些:

·emptyParams:传递{}忽略任何参数。
例子:

DWRActionUtil.execute('/ajax/TestFM',{},doOnJSResult,"stream...");

·fieldId:被转换为action调用参数的字段的id
例子:

·<inputid="mytext"name="mytext"value="somevalue"type="text"/>

DWRActionUtil.execute('/ajax/TestFM','mytext',doOnJSResult,"stream...");

·formId:表单的id.所有的input值被转换为action调用参数。

Note:如果你的action使用了parameter拦截器,那么你的action会得到正确的参数值,请参考WebWork的文档。

callback可以是:

·callbackFunction:DWR中,这个函数在请求完毕后调用。

·callbackObject:DWR中,callback对象。

最后displayMessage是可选参数,当请求完毕后显示的消息(参考DWR文档)

高级

你可以声明一个pre/postAction处理器,在web.xml中的一个context-wide初始化参数(dwrActionProcessor)。处理器必须实现org.directwebremoting.webwork.IDWRActionProcessor接口。这个处理器将会在action之前和之后被调用,所以你可以做一些预处理或改变结果。

JSF整合

DWR包括两个JSF的扩展点,一个创造器和一个ServletFilter

'jsf'创造器

DWR1.1中有一个体验版的JsfCreator。你可以哉dwr.xml中这样使用:

<allow>

...

<createcreator="jsf"javascript="ScriptName">

<paramname="managedBeanName"value="beanName"/>

<paramname="class"value="your.class"/>

</create>

...

</allow>

这将允许你通过DWR调用ManagedBean

TheServletFilter

DWR/Faces过滤器允许你不在JSF的生命周期里调用FacesContext中的Bean

要使用JsfCreator,你应该把DWR/Faces过滤器加到web.xml中。

<filter>

<filter-name>DwrFacesFilter</filter-name>

<filter-class>uk.ltd.getahead.dwr.servlet.FacesExtensionFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>DwrFacesFilter</filter-name>

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

</filter-mapping>

这两个需要放在web.xml中与其他的filterfilter-mapping放在一起。

整合Struts

DWR几乎可以和任何Framework一起工作。这个网站(DWR的官方网站)就是这一点的有力证明,因为它是在Drupal(PHP)中使用DWR

DWRStruts整合有两个层次。最基础的层次就是同时使用两个Framework,这是非常容易的,但是这样就不允许在DWRStruts之间共享Action了。

DWR可以调用任何方法,所以没有理由不让你从DWR调用StrutsAction,除非你不想这样用。ActionForm的内容是什么,当返回ActionForwardDWR怎么做?

一个比较好方法是重构你想调用的Action,提取出Action的逻辑。DWR和你的Action就可以同时调用相同的方法了。

The'struts'创造器

DWR1.1增加了一个StrutsCreator。你可以哉dwr.xml中这样使用:

<allow>

...

<createcreator="struts"javascript="ScriptName">

<paramname="formBean"value="formBeanName"/>

</create>

...

</allow>

这样就允许你在DWR中调用FormBean了。

开始顺序

如果你要使用StrutsCreator那么你保证StrutsDWR之前被初始化。你可以在web.xml中把Struts<load-on-startup>设置的比DWR低。

DWRHibernate

DWRHibernate正常工作的检查列表

1.确保你使用的是最新的DWRHibernate转换器是新东西,所以你需要下载最新的

2.确保你已经明白开始指南上所写的内容。

3.确保你的Hiberante在没有DWR的时候工作正常。

4.如果是SpringHibernate一起使用,那么你最好先了解一下如何将整合Spring

5.配置DWR,使之与Hibernate一起工作。(看下面)

6.查看演示页面:http://localhost:8080/YOUR-WEBAPP/dwr,确定SpringBean可以出现。

HibernateBeanConverter

这个转换器同标准的BeanConverter非常相似,不同之处在于我们可以决定如何处理延迟加载。

使用HibernateBeanConverter可能会带来如下风险:

·架构:HibernateBeanConverter不符合MVC模式,所以不能把对象在数据曾和表现曾之间进行隔离。这个风险可以通过在上面加上独立的bean来减轻。

·性能:DWR试图通过相同的序列化方式来转换所有可以得到的属性(除了DWR仅仅读JavaBean属性的时候)。所以可能会出现通过HTTP序列化了你的整个数据的情况。通常这并不是你想要的。要减少这一风险可以使用BeanConverter(HibernateBeanConverter衍生于它)的排除某些属性的功能:

<paramname="exclude"value="propertyToExclude1,propertyToExclude2"/>

HibernateBeanConverter会尝试不去读取没有初始化的属性。如果你只是想读取所有的东西那么应该使用BeanConverter

建议使用Hibernate3,实际上Hibernate2一下的情况,你会发现你得到的都是空的Bean

Session管理

如果你使用Hibernate对象,你需要知道每一个DWR请求都是一个新的Servlet请求,所以你需要保证为每个请求打开一个HiberanteSession

如果你用Spring,那么可以很方便的使用Spring里面的OpenSessionInViewFilter,它可以保证为每个请求打开一个HiberanteSession。类似的解决方案在其它Framework中也存在。

从其他的URL读取数据

如果你需要读取其他web应用程序生成的页面,并返回到Javascript中。非常简单。只要在你的Java类里面包括下面这写代码:

publicStringgetInclude()throwsServletException,IOException

{

returnWebContextFactory.get().forwardToString("/forward.jsp");

}

很明显你应该把"/forward.jsp"替换成你要forward到的页面的URL。这个URL必须以一个斜杠开始,因为它只是调用HttpRequest.forward()

你可以用这个方法在传递之前任何定制你的页面。

安全

我们很谨慎的对待DWR的安全问题,并且认为有必要解释一下避免错误要做的事情。

首先DWR让你明确哪些是被远程调用的,是如何被远程调用。原则就是DWR必须调用那些你明确允许的代码。

dwr.xml要求你为每一个远程类定义一个'create'项。你还可以通过指定includeexclude元素来更精确的控制远程调用Bean中可以被调用的方法。

除此之外如果你希望允许DWR在转换你的JavaBeanJavascript或者从Javascript转换到JavaBean时有一定的许可限制,同样可以精确控制哪些Bean的属性可以被转换。

一个很明显但又必须指出的不要在生产环境中打开test/debug模式控制台。如何打开或关闭debug控制台在配置web.xml部分可以找到详细描述。

审查-DWR带来的最大好处

很值得对比一下DWRServletJSP或周围的其他web框架。

如果你要审查基于DWR的功能,那是非常简单的。看看dwr.xml你就能得到一个哪些方法被暴露到外面的描述了。你也可以俯视全局用DWR可以访问哪些资源。

但是要在其他系统里做这件事可不是这么容易。如果是Servlet你需要检查WEB-INF/web.xml文件,然后检查写在Servlet中的request.getParameter(...)。如果是Struts和其他Framework你需要检查配置文件,然后顺着流程检查代码,看请求信息出了什么问题。

访问控制

DWR允许你通过两种基于J2EE的机制来进行访问控制。首先你可以基于J2EE角色定义DWR的访问。其次你可以在DWR里面定义访问方法的角色

其他方面

DWR不允许你定义任何内部类的createconvert。这样设计是为了不出现意外的攻击来操作DWR的核心文件以提升访问权限。

风险

有什么机会可以让攻击者窥视你的系统呢?使用DWR你攻击者可以使服务器创建任何你在dwr.xml中指定的Java对象的实例。并且(如果你用BeanConverter)Java类的任何方法、以及方法任何参数都是可见的。这些类的任何一个属性都有可能是攻击者需要的。

如果你知道DWR是怎么工作的,这些都是很显而易见的结论,但是往往粗心会造成问题。如果你创建了一个有appendStringToFile()方法的FileBean的类,而且用DWR把它暴露出去,那么你就给了攻击者一个机会来填满你的文件系统。

你必须时刻注意用了DWR以后,有没有给攻击者什么机会。

一般来说这样的情景让人感觉使用DWR是有风险的,但是这样的问题在所有的传统web架构中都存在,只是在那些架构中这些不明显,所以就很难被修复。

但是我是一个妄想狂

这已经很安全了,那么你还能做什么来保证更加安全了?首先记住上面这些关于审查的内容,当web应用的其他地方不安全时,即使它看上去很安全,过多的关注DWR是很愚蠢的。如果DWR让人感觉恐惧,那是因为它的问题都在明处。所以第一步是多检查几遍传统架构的问题。

你可以通过把可远程访问的类放到不同的包里,并且使用代理来防止DWR访问机制出问题。如果你愿意还可以再次检查基于角色的安全控制。这些内容只是在检查DWR已经为你做的事情。

比多检查几次更好的方法是检查DWR的源码,保证它是在正确的工作。这些代码已经被很多人检查过了,但多双眼睛总是有好处的。

整合Acegi

DWR可以整合Acegisecurityframework。更多的信息见整合DWRAcegi.

Disclaimer

尽管我们很谨慎的对待DWR的安全,但是我们不能声称DWR是绝对安全的,如果这样做是很愚蠢的。你的web站点的安全是你的责任。DWR可以通过在开源授权提供源码来帮你,你可以自己研究,我们鼓励你这样做,并且很多已经这样做了。

浏览器支持

我们希望能测试所有的浏览器,所以如果你可以提供下面列表中任何浏览器的更多信息,或没有列出的浏览器的信息,请加入DWR用户邮件列表

Firefox

版本

状态

1.0

支持:没发现问题

0.7

很少人会用1.0以前的版本,所以尽管我们相信DWR0.7开始就可以在Firefox上工作,但是我们没有计划在Firefox上测试1.0以前的版本

InternetExplorer

版本

状态

6.0(Win)

支持:没发现问题。

5.5(Win)

相信可以工作,但是应该做更多的测试。

5.0.2(Win)

大多功能没问题,但是有报告说丢失信息。

5.2(OSX)

不能工作,并且没有计划支持Mac上的IE

Netscape或其他的基于Mozilla的浏览器

版本

状态

1.7

支持Mozilla1.7以及基于它的浏览器,尽管我们当前只在Mozilla1.7测试过。我们希望发现问题的人能告诉我们。

Opera

版本

状态

8.0(Win)

支持,大多工作良好,字符串为null可能出现小问题。你不必担心这个影响,除非你做的非常奇怪。

7.5.4(Win)

支持,和8.0一样。

6.0.6(Win)

不支持。

8.0(OSX)

需要测试。

Safari

版本

状态

1.3(OSX)

支持,没发现问题。

1.2(OSX)

支持,和1.3结果一样,需要更多测试。

1.1(OSX)

不清楚

1.0.3(OSX)

不支持

Konqueror

版本

状态

未知

有报告称最新版的Konqueror可以,但是没有早期版本的报告。

支持的环境

DWR需要JDK1.3以上的版本,一个支持2.2标准版本以上的Servlet引擎。早期版本的Servlet标准也许也能工作。

测试过的环境包括:

·Tomcat

·Weblogic

·Websphere

·JBoss

·Jetty

·Resin

·SunONE

·Glashfish

1.0版中我们知道两个环境可能有一些问题。当应用服务器的前端被web服务器代理时,web服务器可能重写URL,这样应用服务器可能就不认识这个URL,这时DWR可能出问题。另外DWR运行在VignettePortal里也可能出问题。

在其他的测试过的环境里DWR应该是没什么问题的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值