如何开始用DWR
有两种方法开始DWR,简单的方式是下载WAR文件然后看看。但是这不能帮你知道如何轻松的把DWR整合到你的web应用中,所以还是推荐你按照下面的3个步骤做:
下载dwr.jar文件。把它放到你的webapp的WEB-INF/lib目录下。那里可能已经有很多其他的jar文件了。
需要把下面的代码加到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中使用。 |
http://localhost:8080/[YOUR-WEBAPP]/dwr/
你可以看见一个页面,里面有第二步中的类。接着往里点,你会看到所有可以调用的方法列表。这个页面是动态生成用来测试的例子。
自己动手试一下!
在文档中有很多例子演示如何动态更改页面中的文字、更新列表、操作表单,还有直接更改table中的内容。每一个都有如何实现的介绍。
另一种方式是看刚才的页面中提供的代码:
到http://localhost:8080/\[YOUR-WEBAPP\]/dwr/页面,点击你的类。查看源码,找到执行方法的那几行,把那些文字粘贴到你的HTML或JSP中。
要包括下面这些能产生神奇效果的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);
42是Java方法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
这个问题的现象是在启动有DWR的Web应用时出现如下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能更好的处理这个问题,但是本质的问题还是因为DWR的XML序列化需要有XSLT解析器的支持。
如果你用JDK5还是有这个问题的话,你可以增加以下VM参数来使Tomcat正常工作。
-Djavax.xml.transform.TransformerFactory=
com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
在刚开始用DWR的时候经常遇到的一个错误就是XML解析错误。其实这和DWR没有多大关系,主要是因为Tomcat里面自带的Xerces的问题,要不是该有的时候没有,要不是不该有的时候有了。
·JDK1.3自身没有XML解析器,所以你需要xercesImpl.jar和xml-apis.jar.
·JDK1.4.0和JDK1.4.1虽然有了XML解析器,但是有很多bug,所以你还是需要把xercesImpl.jar放到tomcat\common\endorsed目录下。
·JDK1.4.2和JDK5自带的XML解析器工作的很好,你就不需要再加其他的了。
另外要提的一点是,不同版本的Tomcat需要的XML解析器不一样。所以要注意检查它和JDK的版本兼容性。
Weblogic8.1(有可能其他版本同样)可能找不到DWR的类。
这大多出现在dwr.jar放在APP-INF目录下(APP_INF/lib)的情况。在这种情况下DWR依然可以工作,例如debug页面可以看见,但是DWR找不到你的类。
解决办法是把dwr.jar放到WEB-INF/lib目录下。
当不能用cookies时,servlet规范通过URL重写来支持HttpSession。DWR2.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函数,但是因为所有的回调函数都只有一个参数(远程方法的返回结果),这就需要一些小技巧了。
解决方案就是使用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级别设置ERROR或FATAL,但是主要还是要看你的编码情况。
Network瓶颈:DWR没有管理你的浏览器缓存的功能,所以它会不断的重复读取DWR的javascript文件。这里有一个简单的解决办法,把javascript文件复制到你的web-app中,这样web服务器就可以更好的利用它了。你也可以考虑把所有的javascript文件合并成一个文件,然后用DOJO的压缩程序处理一个来节省流量。
我们可以做一个补丁,让DWR在web-app启动的时候用时间做为javascript文件的时间戳,但是这个并不十分重要,因为上面的补丁太简单了而且可以压缩合并Javascript文件。
在web.xml中最简单的配置就是简单加入DWR的servlet,没有这个配置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>
此外还可以加入一些重要和有用的参数。
DWR可以工作在JDK1.3上,而JDK1.3不支持java.util.logging,但是我们想强迫任何人使用commons-logging或者log4j,所以当没有logging类的时候DWR就使用HttpServlet.log()方法。尽管如此,如果DWR发现了commons-logging,就是使用它。
几乎每一个人都在使用commons-logging,因为大多数的servlet容器在使用它。所以如果你的web应用中没有明显的加入commons-logging包,它也会默认的配置好。
在这种情况下,logging是由java.util.logging或者log4j配置文件控制的。详细配置查看文档。
如果你用HttpServlet.log(),下面的配置控制logging:
<init-param>
<param-name>logLevel</param-name>
<param-value>DEBUG</param-value>
</init-param>
可用的值有:FATAL,ERROR,WARN(默认),INFO和DEBUG。
一般来说,你只需要一个dwr.xml文件,并且放置在默认的位置:WEB-INF/dwr.xml。如果那样的话,你可以不用了解下面的配置。
有三个原因使你希望指定不同位置的dwr.xml文件。
·你希望让dwr.xml文件和它能访问到的资源在一起。在这种情况下你需要一个这样的配置:<param-value>WEB-INF/classes/com/yourco/dwr/dwr.xml</param-value>。
·你有大量的远程调用类,希望把他们分成多个文件。在这种情况下你需要重复下面的配置几次,每一个中有不同的param-name,并且以'config'开头。DWR会依次把他们都读进来。
·DWR可以使用Servlet规范的J2EE的URL安全机制来给不同的用户不同的访问权限。你只需要简单的定义多个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>
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中。
你可以通过下面的参数让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.xml是DWR的配置文件。默认情况下,应该把它放到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。所以如果你有一个叫A的bean,它有一个方法叫A.blah(B)那么你需要一个A的creator和一个B的converter。
<allow>
allow段落里面定义的试DWR可以创建和转换的类。
Creators
我们要调用的每个类都需要一个<create...>定义。creator有几种。比较通用的是new关键字和Spring。更多的信息可以参见[Creaters]文档。
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)
要了解如何转换你的JavaBean或者其他类型的参数请查看Converters文档。
<init>
可选的init部分用来声明创造bean的类和转换bean的类。多数情况下你不需要用到他们。如果你需要定义一个新的Creator[JavaDoc]和Converter[JavaDoc],那么你就需要在这里定义他们。但是建议你现检查一下DWR是不是已经支持了。
在init部分里有了定义只是告诉DWR这些扩展类的存在,给出了如何使用的信息。这时他们还没有被使用。这中方式很像Java中的import语句。多数类需要在使用前先import一下,但是只有import语句并不表明这个类已经被使用了。每一个creator和converter都用id属性,以便后面使用。
<signatures>
DWR使用反射来找出在转换时应该用那种类型。有时类型信息并不明确,这时你可以在这里写下方法的签名来明确类型。详细信息查看Signatures部分。
多个dwr.xml文件
可以有多个dwr.xml文件(详细信息见web.xml文档)。每个文件中的定义会被加在一起。DWR用这个功能来加载基础配置文件。我们可以看看标准被配置文件来了解dwr.xml的内容。
转换器在客户端和服务器之间转换数据.
下面这些转换器有单独章节介绍
·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.BigDecimal和java.lang.String
Date转换器负责在Javascript的Date类型与Java中的Date类型(java.util.Date,java.sql.Date,java.sql.Timesorjava.sql.Timestamp)之间进行转换。同基础的转换器一样,DateConverter默认是支持的。
如果你有一个Javascript的字符串(例如"01Jan2010"),你想把它转换成Java的Date类型有两个办法:在javascript中用Date.parse()把它解析成Date类型,然后用DWR的DateConverter传递给服务器;或者把它作为字符串传递给Server,再用Java中的SimpleDateFormat(或者类似的)来解析。
同样,如果你有个Java的Date类型并且希望在HTML使用它。你可以先用SimpleDateFormat把它转换成字符串再使用。也可以直接传Date给Javascript,然后用Javascript格式化。第一种方式简单一些,尽管浪费了你的转换器,而且这样做也会是浏览器上的显示逻辑受到限制。其实后面的方法更好,也有一些工具可以帮你,例如:
·TheJavascriptToolboxDateformatter
·WebDevelopersNotesonDateformatting
其实创建自己的转换器也很简单。Converter接口的Javadoc包含了信息。其实这种需要很少出现。在你写自己的Converter之前先看看BeanConverter,它有可能就是你要的。
dwr.xml文件中的create元素的结构如下:
<allow>
<createcreator="..."javascript="..."scope="...">
<paramname="..."value="..."/>
<authmethod="..."role="..."/>
<excludemethod="..."/>
<includemethod="..."/>
</create>
...
</allow>
这里的多数元素都是可选的-你真正必须知道的是指定一个creator和一个javascript名字。
creator属性是必须的-它用来指定使用那种创造器。
默认情况下DWR1.1有8种创造器。它们是:
·new:用Java的new关键字创造对象。
·none:它不创建对象,看下面的原因。(v1.1+)
·scripted:通过BSF使用脚本语言创建对象,例如BeanShell或Groovy。
·spring:通过Spring框架访问Bean。
·jsf:使用JSF的Bean。(v1.1+)
·struts:使用Struts的FormBean。(v1.1+)
·pageflow:访问Beehive或Weblogic的PageFlow。(v1.1+)
如果你需要写自己的创造器,你必须在init部分注册它。
javascript属性用于指定浏览器中这个被创造出来的对象的名字。你不能使用Javascript的关键字。
scope属性非常类似servlet规范中的scope。它允许你指定这个bean在什么生命范围。选项有"application","session","request"和"page"。这些值对于Servlet和JSP开发者来说应该相当熟悉了。
scope属性是可选的。默认是"page"。如果要使用"session"需要cookies。当前的DWR不支持ULR重写。
param元素被用来指定创造器的其他参数,每种构造器各有不同。例如,"new"创造器需要知道要创建的对象类型是什么。每一个创造器的参数在各自的文档中能找到。请查看上面的链接。
include和exclude元素允许创造器来限制类中方法的访问。一个创造器必须指定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'创造器不创建任何对象-它会假设你不需要创建对象。这有可能是对的,有两个原因。
你可能在使用的scope不是"page"(看上面),并在在前面已经把这个对象创建到这个scope中了,这时你就不需要再创建对象了。
还有一种情况是要调用的方法是静态的,这时也不需要创建对象。DWR会在调用创建器之前先检查一下这个方法是不是静态的。
对于上诉两种情况,你仍然需要class参数,用来告诉DWR它是在操作的对象类型是什么。
DWR会在调用创建器之前先检查一下这个方法是不是静态的,如果是那么创造器不会被调用。很显然这个逻辑适用于所有创造器,尽管如此"null"创造器是最容易配置的。
对于单例类的创建,最好适用BeanShell和BSF来实例化对象。详细信息参见'Scripted'创造器
我么偶尔也需要一些新的创造器,最常见的是一个EjbCreator。讨论新的创造器的好地方是在邮件列表。
DWR和HttpSessionBindingListeners
DWR1.x中存贮已经创造的Bean的方法需要注意,它在每次请求时都会调用相同的setAttribute()方法。就是说,如果一个Bean在dwr.xml中的声明周期设置为session,再每次调用bean中的方法时,DWR都会执行一次session.setAttribute(yourBean)。这看上去没有什么危害,但是如果你要使用servlet的事件机制的,就是说用了HttpSessionBindingListener接口,你就会发现valueBound和valueUnbound事件在每次调用时都会发生,而不是你想像的在bean被创建时以及session过期时。
DWR2只在第一次创建对象时调用setAttribute()。
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.x用Javascript的特性把key转换成String。DWR2.0可能会用toString()方法,在服务段进行这一转换。
engine.jsFunctions
engine.js对DWR非常重要,因为它是用来转换来至动态生成的接口的javascript函数调用的,所以只要用到DWR的地方就需要它。
Theengine.jsfile
每一个页面都需要下面这些语句来引入主DWR引擎。
<scripttype='text/javascript'
src='/[YOUR-WEB-APP]/dwr/engine.js'>
</script>
使用选项
下面这些选项可以通过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会在单次调用和批量调用之后被调用。
如果以上叙述让你感到混乱,不用担心。DWR的的设计往往和你想象中的一样,所以其实这些并不复杂。
选项索引
下面是可用选项列表。
Option | Global | Batch | Call | Summary |
1.1 | 1.1 | 1.1 | 设置是否为异步调用,不推荐同步调用 | |
headers | 2.0 | 2.0 | 2.0 | 在XHR调用中加入额外的头信息 |
parameters | 2.0 | 2.0 | 2.0 | 可以通过Meta-datarequest.getParameter()取得的元数据 |
2.0 | 2.0 | 2.0 | 选择GET或者POST.1.x中叫'verb' | |
2.0 | 2.0 | 2.0 | 选择是使用xhr,iframe或者script-tag来实现远程调用.1.x中叫'method' | |
1.0* | 2.1? | - | 某个调用是否应该设置为batch中的一部分或者直接的。这个选项和上面都有些不同。 | |
timeout | 1.0 | 1.1 | 1.1 | 设定超时时长,单位ms |
处理器(Handler)
Option | Global | Batch | Call | Summary |
1.0 | 1.1 | 1.1 | 当出了什么问题时的动作。1.x中还包括服务端的异常。从2.0开始服务端异常通过'exceptionHandler'处理 | |
1.0 | 2.0 | 2.0 | 当因为浏览器的bug引起问题时的动作,所以默认这个设置为null(关闭) | |
textHtmlHandler | 2.0 | 2.0 | 2.0 | 当得到不正常的text/html页面时的动作(通常表示超时) |
调用处理器(CallHandler)(注册到单独调用上的,而不是batch中的所有调用)
Option | Global | Batch | Call | Summary |
- | - | 1.0 | 调用成功以后的要执行的回调函数,应该只有一个参数:远程调用得到的数据 | |
exceptionHandler | - | - | 2.0 | 远程调用失败的动作,一般是服务端异常或者数据转换问题。 |
Hooks(一个batch中可以注册多个hook)
Option | Global | Batch | Call | Summary |
1.0 | 1.1 | 1.1 | 远程调用前执行的函数 | |
1.0 | 1.1 | 1.1 | 远程调用后执行的函数 |
全局选项(在单次调用或者批量调用中不可用)
Option | Global | Batch | Call | Summary |
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废弃。使用'httpMethod'代替 |
method | 1.0 | 1.1 | 1.1 | 2.0废弃。使用'rpcType'代替 |
将来的
Option | Global | Batch | Call | Summary |
onBackButton | 2.1? | 2.1? | - | 用户按了back按钮后的动作 |
onForwardButton | 2.1? | 2.1? | - | 用户按了forward按钮的动作 |
保证的责任
DWR的目的是让你确切的知道所有调用的动作。知道了浏览器存在的bug,这是可以做到了。
如果你设置了callback,exceptionHandler,errorHandler,warningHandler和textHtmlHandler,DWR就应该总是为每一个请求提供响应。
你可以使用batch来批量的执行远程调用。这样可以减少与服务器的交互次数,所以可以提交反应速度。
一个batch以DWREngine.beginBatch()开始,并以DWREngine.endBatch()结束。当DWREngine.endBatch()被调用,我们就结束了远程调用的分组,这样DWR就在一次与服务器的交互中执行它们。
DWR会小心的处理保证所有的回调函数都会被调用,所以你可以明显的打开和关闭批处理。只要别忘了调用endBatch(),否则所有的远程调用永远的处于列队中。
很明显,把一些远程调用放在一起执行也会产生一些影响。例如不能在batch里面执行同步调用。
所有的元数据选项,例如hooks,timeouts和errorHandlers都在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"消息就会被显示。
DWREngine.setPreHook(function)和DWREngine.setPostHook(function)。
如果你想在DWR调用之前出现一个提示,你可以设置pre-hook函数。它将会被调用,但是没有参数传递到这个函数。当你希望让一些按钮在调用期间变灰来防止被再次使用,这一功能将会很有用。
post-hook用来和pre-hook一起使用来逆转pre-hook产生的做的一些改变。
一个使用pre和posthook的例子就是DWRUtil.useLoadingMessage()函数。
DWR有一些选项用来控制远程调用的处理方式。method和verb对于用户应该时透明的,但是不同的浏览器效果的可能不一样。一般来说DWR会选择正确方法来处理,但是这些选项对于在不同效果的浏览器之间开发很有用。
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
});
这个选项允许你选择POST和GET,无论时用iframe还是XMLHttpRequest方法。一些浏览器(例如,旧版的Safari)不支持XHR-POST所以DWR就自动切换到GET,即使你设置POST为verb。所以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包含了一些工具函数来帮助你用javascript数据(例如从服务器返回的数据)来更新你的web页面。
你可以在DWR以外使用它,因为它不依赖于DWR的其他部分。你可以下载整个DWR或者单独下载.
4个基本的操作页面的函数:getValue[s]()和setValue[s]()可以操作大部分HTML元素除了table,list和image。getText()可以操作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找个替代品。
DWR的一个常遇到的任务就是根据选项填充选择列表。下面的例子就是根据输入填充列表。
下面将介绍DWRUtil.addOptions()的几种是用方法。
如果你希望在你更新了select以后,它仍然保持运来的选择,你要像下面这样做:
varsel=DWRUtil.getValue(id);
DWRUtil.removeAllOptions(id);
DWRUtil.addOptions(id,...);
DWRUtil.setValue(id,sel);
如果你想加入一个初始的"Pleaseselect..."选项那么你可以直接加入下面的语句:
DWRUtil.addOptions(id,\["Pleaseselect..."]);
然后再下面紧接着加入你真正的选项数据。
数组:DWRUtil.addOptions(selectid,array)会创建一堆option,每个option的文字和值都是数组元素中的值。
对象数组(指定text):DWRUtil.addOptions(selectid,data,prop)用每个数组元素创造一个option,option的值和文字都是在prop中指定的对象的属性。
对象数组(指定text和value值):DWRUtil.addOptions(selectid,array,valueprop,textprop)用每个数组元素创造一个option,option的值是对象的valueprop属性,option的文字是对象的textprop属性。
对象:DWRUtil.addOptions(selectid,map,reverse)用每个属性创建一个option。对象属性名用来作为option的值,对象属性值用来作为属性的文字,这听上去有些不对。但是事实上却是正确的方式。如果reverse参数被设置为true,那么对象属性值用来作为选项的值。
对象的Map:DWRUtil.addOptions(selectid,map,valueprop,textprop)用map中的每一个对象创建一个option。用对象的valueprop属性做为option的value,用对象的textprop属性做为option的文字。
ol或ul列表:DWRUtil.addOptions(ulid,array)用数组中的元素创建一堆li元素,他们的innerHTML是数组元素中的值。这种模式可以用来创建ul和ol列表。
DWR通过这两个函数来帮你操作table:DWRUtil.addRows()和DWRUtil.removeAllRows()。这个函数的第一个参数都是table、tbody、thead、tfoot的id。一般来说最好使用tbody,因为这样可以保持你的header和footer行不变,并且可以防止InternetExplorer的bug。
语法:
DWRUtil.removeAllRows(id);
描述:
通过id删除table中所有行。
参数:
·id:table元素的id(最好是tbody元素的id)
语法:
DWRUtil.addRows(id,array,cellfuncs,[options]);
描述:
向指定id的table元素添加行。它使用数组中的每一个元素在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")
getText(id)和getValue(id)很相似。出了它是为select列表设计的。你可能需要取得显示的文字,而不是当前选项的值。
DWRUtil.getValue(id)是setValue()对应的"读版本"。它可以从HTML元素中取出其中的值,而你不用管这个元素是select列表还是一个div。
这个函数能操作大多数HTML元素包括select(去处当前选项的值而不是文字)、input元素(包括textarea)、div和span。
getValues()和getValue()非常相似,除了输入的是包含name/value对的javascript对象。name是HTML元素的ID,value会被更改为这些ID对象元素的内容。这个函数不会返回对象,它只更改传递给它的值。
从DWR1.1开始getValues()可以传入一个HTML元素(一个DOM对象或者id字符串),然后从它生成一个reply对象。
当按下return键时,得到通知。
当表单中有input元素,触发return键会导致表单被提交。当使用Ajax时,这往往不是你想要的。而通常你需要的触发一些Javscript。
不幸的是不同的浏览器处理这个事件的方式不一样。所以DWRUtil.onReturn修复了这个差异。如果你需要一个同表单元素中按回车相同的特性,你可以用这样代码实现:
<inputtype="text"οnkeypress="DWRUtil.onReturn(event,submitFunction)"/>
<inputtype="button"οnclick="submitFunction()"/>
你也可以使用onkeypress事件或者onkeydown事件,他们做同样的事情。
一般来说DWR不是一个Javascript类库,所以它应该试图满足这个需求。不管怎样,这是在使用Ajax过程中一个很有用函数。
这个函数的工作原理是onSubmit()事件只存在于<FORM...>元素上。
选择一个输入框中的一定范围的文字。
你可能为了实现类似"Googlesuggest"类型的功能而需要选择输入框中的一定范围的文字,但是不同浏览器间选择的模型不一样。这DWRUtil函数可以帮你实现。
DWRUtil.setValue(id,value)根据第一个参数中指定的id找到相应元素,并根据第二个参数改变其中的值。
这个函数能操作大多数HTML元素包括select(去处当前选项的值而不是文字)、input元素(包括textarea)、div和span。
setValues()和setValue()非常相似,除了输入的是包含name/value对的javascript对象。name是HTML元素的ID,value是你想要设置给相应的元素的值。
DWRUtil.toDescriptiveString()函数比默认的toString()更好。第一个参数是要调试的对象,第二个参数是可选的,用来指定内容深入的层次:
·0:单行调试
·1:多行调试,但不深入到子对象。
·2:多行调试,深入到第二层子对象
以此类推。一般调试到第二级是最佳的。
还有第三个参数,定义初始缩进。这个函数不应该被用于调式程序之外,因为以后可能会有变化。
设置一个Gmail风格的加载信息。所有演示页面(dynamictext,selectionlists,livetables,liveforms,dynamicvalidation和addressentry)都使用了GMail风格的加载消息。
这个方法将来可能被废弃,因为这个实现实在太专断了。为什么是红色,为什么在右上角,等等。唯一的真正答案就是:抄袭GMail。这里的建议是以本页面中的代码为模板,根据你的需求自定义。
你必须在页面加载以后调用这个方法(例如,不要在onload()事件触发之前调用),因为它要创建一个隐藏的div来容纳消息。
最简单的做法时在onload事件中调用DWRUtil.useLoadingMessage,像这样:
<head>
<script>
functioninit(){
DWRUtil.useLoadingMessage();
}
</script>
...
</head>
<bodyοnlοad="init();">
...
可能有些情况下你是不能容易的编辑header和body标签(如果你在使用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(id是disabledZone)来容纳消息。重要的代码是当远程调用时使它显示和隐藏:
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");
h1非util.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版中错误处理规则有些bug,1.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对象会包含异常在服务端的所有属性,但是异常栈信息没有。
1.确认你用的是最新版的DWR。Spring创造器已经有了变化,所以你最好检查一下DWR的最新版本。
2.确认你查看过开始指南中的内容。
3.确认你的Spring的Bean在DWR外面运行良好。
4.配置DWR和Spring一起工作。(看下面)
5.查看演示页面:http://localhost:8080/[YOUR-WEBAPP]/dwr,检查spring的Bean是否出现。
DWR对于Spring没有运行期依赖,所以如果你不使用Spring那么Spring的支持不会产生任何影响到。
这个创造器会查找spring的中配置的Bean,用Spring去创建它们。如果你已经在使用Spring,那么这个创造器会非常有用。
你可以通过下面的方式来创建远程调用的Bean:
<allow>
...
<createcreator="spring"javascript="Fred">
<paramname="beanName"value="Shiela"/>
</create>
</allow>
有三种方式寻找配置文件:
最简单的方式是使用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的文档。
如果你要在dwr.xml中指定使用哪些bean,你可以使用location*参数。你可以指定任意多个文件,只要参数以location开始并且唯一即可。例如:location-1,location-2。这些location被用做Spring的ClassPathXmlApplicationContext的参数:
<allow>
...
<createcreator="spring"javascript="Fred">
<paramname="beanName"value="Shiela"/>
<paramname="location"value="beans.xml"/>
</create>
</allow>
SpringCreator有一个静态方法setOverrideBeanFactory(BeanFactory)用来通过编程的方式直接设置BeanFactory。
BramSmeets写了一个有意思的blog,教你配置DWR使用beans.xml代替WEB-INF/web.xml。
我也对于如何在beans.xml中指定dwr.xml很感兴趣,尽管这看上去有些Spring传染病的感觉。有人知道如何实现吗?请加入邮件列表并告诉大家。
WebWork支持在DWR2.0m3以后才有。
要可以通过DWR调用WW的Action,要做两件事。
你必须在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>.*"/>
下面这些代码开启DWR调用Action的功能。你还要导入DWRActionUtil.js脚本(在你的web脚本路径中)
使用
像这样在JS中调用Action:
DWRActionUtil.execute(id,params,callback[,displayMessage]);
id参数可以是下面这些:
·actionUri:要调用action的URI(没有.action).例如:
DWRActionUtil.execute('/ajax/TestFM','myform','doOnTextResult');
·actionDefinitionObject:在xwork.xml中定义的action对象.必须指定下面的内容:
onamespace:xwork.xml中action的名称空间
oaction:xwork.xml中action的名字
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之前和之后被调用,所以你可以做一些预处理或改变结果。
DWR包括两个JSF的扩展点,一个创造器和一个ServletFilter。
DWR1.1中有一个体验版的JsfCreator。你可以哉dwr.xml中这样使用:
<allow>
...
<createcreator="jsf"javascript="ScriptName">
<paramname="managedBeanName"value="beanName"/>
<paramname="class"value="your.class"/>
</create>
...
</allow>
这将允许你通过DWR调用ManagedBean。
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中与其他的filter和filter-mapping放在一起。
DWR几乎可以和任何Framework一起工作。这个网站(DWR的官方网站)就是这一点的有力证明,因为它是在Drupal(PHP)中使用DWR。
DWR和Struts整合有两个层次。最基础的层次就是同时使用两个Framework,这是非常容易的,但是这样就不允许在DWR和Struts之间共享Action了。
DWR可以调用任何方法,所以没有理由不让你从DWR调用Struts的Action,除非你不想这样用。ActionForm的内容是什么,当返回ActionForward时DWR怎么做?
一个比较好方法是重构你想调用的Action,提取出Action的逻辑。DWR和你的Action就可以同时调用相同的方法了。
DWR1.1增加了一个StrutsCreator。你可以哉dwr.xml中这样使用:
<allow>
...
<createcreator="struts"javascript="ScriptName">
<paramname="formBean"value="formBeanName"/>
</create>
...
</allow>
这样就允许你在DWR中调用FormBean了。
如果你要使用StrutsCreator那么你保证Struts在DWR之前被初始化。你可以在web.xml中把Struts的<load-on-startup>设置的比DWR低。
DWR和Hibernate
让DWR和Hibernate正常工作的检查列表
1.确保你使用的是最新的DWR。Hibernate转换器是新东西,所以你需要下载最新的
2.确保你已经明白开始指南上所写的内容。
3.确保你的Hiberante在没有DWR的时候工作正常。
4.如果是Spring和Hibernate一起使用,那么你最好先了解一下如何将整合Spring。
5.配置DWR,使之与Hibernate一起工作。(看下面)。
6.查看演示页面:http://localhost:8080/YOUR-WEBAPP/dwr,确定Spring的Bean可以出现。
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请求,所以你需要保证为每个请求打开一个Hiberante的Session。
如果你用Spring,那么可以很方便的使用Spring里面的OpenSessionInViewFilter,它可以保证为每个请求打开一个Hiberante的Session。类似的解决方案在其它Framework中也存在。
如果你需要读取其他web应用程序生成的页面,并返回到Javascript中。非常简单。只要在你的Java类里面包括下面这写代码:
publicStringgetInclude()throwsServletException,IOException
{
returnWebContextFactory.get().forwardToString("/forward.jsp");
}
很明显你应该把"/forward.jsp"替换成你要forward到的页面的URL。这个URL必须以一个斜杠开始,因为它只是调用HttpRequest.forward()。
你可以用这个方法在传递之前任何定制你的页面。
我们很谨慎的对待DWR的安全问题,并且认为有必要解释一下避免错误要做的事情。
首先DWR让你明确哪些是被远程调用的,是如何被远程调用。原则就是DWR必须调用那些你明确允许的代码。
dwr.xml要求你为每一个远程类定义一个'create'项。你还可以通过指定include和exclude元素来更精确的控制远程调用Bean中可以被调用的方法。
除此之外如果你希望允许DWR在转换你的JavaBean到Javascript或者从Javascript转换到JavaBean时有一定的许可限制,同样可以精确控制哪些Bean的属性可以被转换。
一个很明显但又必须指出的–不要在生产环境中打开test/debug模式控制台。如何打开或关闭debug控制台在配置web.xml部分可以找到详细描述。
很值得对比一下DWR和Servlet、JSP或周围的其他web框架。
如果你要审查基于DWR的功能,那是非常简单的。看看dwr.xml你就能得到一个哪些方法被暴露到外面的描述了。你也可以俯视全局用DWR可以访问哪些资源。
但是要在其他系统里做这件事可不是这么容易。如果是Servlet你需要检查WEB-INF/web.xml文件,然后检查写在Servlet中的request.getParameter(...)。如果是Struts和其他Framework你需要检查配置文件,然后顺着流程检查代码,看请求信息出了什么问题。
DWR允许你通过两种基于J2EE的机制来进行访问控制。首先你可以基于J2EE角色定义DWR的访问。其次你可以在DWR里面定义访问方法的角色。
DWR不允许你定义任何内部类的create和convert。这样设计是为了不出现意外的攻击来操作DWR的核心文件以提升访问权限。
有什么机会可以让攻击者窥视你的系统呢?使用DWR你攻击者可以使服务器创建任何你在dwr.xml中指定的Java对象的实例。并且(如果你用BeanConverter)Java类的任何方法、以及方法任何参数都是可见的。这些类的任何一个属性都有可能是攻击者需要的。
如果你知道DWR是怎么工作的,这些都是很显而易见的结论,但是往往粗心会造成问题。如果你创建了一个有appendStringToFile()方法的FileBean的类,而且用DWR把它暴露出去,那么你就给了攻击者一个机会来填满你的文件系统。
你必须时刻注意用了DWR以后,有没有给攻击者什么机会。
一般来说这样的情景让人感觉使用DWR是有风险的,但是这样的问题在所有的传统web架构中都存在,只是在那些架构中这些不明显,所以就很难被修复。
这已经很安全了,那么你还能做什么来保证更加安全了?首先记住上面这些关于审查的内容,当web应用的其他地方不安全时,即使它看上去很安全,过多的关注DWR是很愚蠢的。如果DWR让人感觉恐惧,那是因为它的问题都在明处。所以第一步是多检查几遍传统架构的问题。
你可以通过把可远程访问的类放到不同的包里,并且使用代理来防止DWR访问机制出问题。如果你愿意还可以再次检查基于角色的安全控制。这些内容只是在检查DWR已经为你做的事情。
比多检查几次更好的方法是检查DWR的源码,保证它是在正确的工作。这些代码已经被很多人检查过了,但多双眼睛总是有好处的。
DWR可以整合Acegisecurityframework。更多的信息见整合DWR和Acegi.
尽管我们很谨慎的对待DWR的安全,但是我们不能声称DWR是绝对安全的,如果这样做是很愚蠢的。你的web站点的安全是你的责任。DWR可以通过在开源授权下提供源码来帮你,你可以自己研究,我们鼓励你这样做,并且很多已经这样做了。
我们希望能测试所有的浏览器,所以如果你可以提供下面列表中任何浏览器的更多信息,或没有列出的浏览器的信息,请加入DWR用户邮件列表。
版本 | 状态 |
1.0 | 支持:没发现问题 |
0.7 | 很少人会用1.0以前的版本,所以尽管我们相信DWR从0.7开始就可以在Firefox上工作,但是我们没有计划在Firefox上测试1.0以前的版本 |
版本 | 状态 |
6.0(Win) | 支持:没发现问题。 |
5.5(Win) | 相信可以工作,但是应该做更多的测试。 |
5.0.2(Win) | 大多功能没问题,但是有报告说丢失信息。 |
5.2(OSX) | 不能工作,并且没有计划支持Mac上的IE。 |
版本 | 状态 |
1.7 | 支持Mozilla1.7以及基于它的浏览器,尽管我们当前只在Mozilla1.7测试过。我们希望发现问题的人能告诉我们。 |
版本 | 状态 |
8.0(Win) | 支持,大多工作良好,字符串为null可能出现小问题。你不必担心这个影响,除非你做的非常奇怪。 |
7.5.4(Win) | 支持,和8.0一样。 |
6.0.6(Win) | 不支持。 |
8.0(OSX) | 需要测试。 |
版本 | 状态 |
1.3(OSX) | 支持,没发现问题。 |
1.2(OSX) | 支持,和1.3结果一样,需要更多测试。 |
1.1(OSX) | 不清楚 |
1.0.3(OSX) | 不支持 |
版本 | 状态 |
未知 | 有报告称最新版的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应该是没什么问题的。