如何开始用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>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有一些限制:
· 不要出现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文件的链接。
<script src='/[YOUR-WEBAPP]/dwr/interface/[YOUR-SCRIPT].js'></script>
<script src='/[YOUR-WEBAPP]/dwr/engine.js'></script>
你也可以把其中/[YOUR-WEBAPP]/替换成你的web页面的相对路径。
DWR根据dwr.xml生成和Java代码类似的Javascript代码。
相对而言Java同步调用,创建与Java代码匹配的Ajax远程调用接口的最大挑战来至与实现Ajax的异步调用特性。
DWR通过引入回调函数来解决这个问题,当结果被返回时,DWR会调用这个函数。
有两种推荐的方式来使用DWR实现远程方法调用。可以通过把回调函数放在参数列表里,也可以把回调函数放到元数据对象里。
当然也可以把回调函数做为第一个参数,但是不建议使用这种方法。因为这种方法在处理自动处理http对象时(查看"Alternative Method")上会有问题。这个方法主要是为向下兼容而存在的。
简单的回调函数
假设你有一个这样的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()的一个参数。
此外你也可以使用这种减缩格式:
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方法:
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不会向它传递任何参数。
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
在刚开始用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的版本兼容性。
Weblogic 8.1(有可能其他版本同样)可能找不到DWR的类。
这大多出现在dwr.jar放在APP-INF目录下(APP_INF/lib)的情况。在这种情况下DWR依然可以工作,例如debug页面可以看见,但是DWR找不到你的类。
解决办法是把dwr.jar放到WEB-INF/lib目录下。
当不能用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+默认支持。
通常我们需要传递额外的数据到callback函数,但是因为所有的回调函数都只有一个参数(远程方法的返回结果),这就需要一些小技巧了。
解决方案就是使用Javascript的闭包特性。
例如,你的回调函数原本需要像这个样子:
function callbackFunc(dataFromServer, dataFromBrowser) {
// 用dataFromServer和dataFromBrowser做些事情......
}
那么你可以像这个组织你的函数:
var dataFromBrowser = ...;
// 定义一个闭包函数来存储dataFromBrowser的引用,并调用dataFromServer
var callbackProxy = function(dataFromServer) {
callbackFunc(dataFromServer, dataFromBrowser);
};
var callMetaData = { callback:callbackProxy };
Remote.method(params, callMetaData);
(调用元数据在脚本介绍中有解释)
换句话说,现在你作为callback函数传递过来的不是一个真正的callback,他只是一个做为代理的闭包,用来传递客户端的数据。
你可以用更简介的形式:
var dataFromBrowser = ...;
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安全机制来给不同的用户不同的访问权限。你只需要简单的定义多个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要以字符串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文件的结构如下:
<!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>
术语
这里是一些必须理解的术语 - 参数会被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的内容。
转换器在客户端和服务器之间转换数据.
下面这些转换器有单独章节介绍
· Servlet Objects (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.Times or java.sql.Timestamp)之间进行转换。同基础的转换器一样,DateConverter默认是支持的。
如果你有一个Javascript的字符串 (例如"01 Jan 2010") ,你想把它转换成Java的Date类型有两个办法:在javascript中用Date.parse()把它解析成Date类型,然后用DWR的DateConverter传递给服务器;或者把它作为字符串传递给Server,再用Java中的SimpleDateFormat(或者类似的)来解析。
同样,如果你有个Java的Date类型并且希望在HTML使用它。你可以先用SimpleDateFormat把它转换成字符串再使用。也可以直接传Date给Javascript,然后用Javascript格式化。第一种方式简单一些,尽管浪费了你的转换器,而且这样做也会是浏览器上的显示逻辑受到限制。其实后面的方法更好,也有一些工具可以帮你,例如:
· The Javascript Toolbox Date formatter
· Web Developers Notes on Date formatting
其实创建自己的转换器也很简单。Converter接口的Javadoc包含了信息。其实这种需要很少出现。在你写自己的Converter之前先看看BeanConverter,它有可能就是你要的。
dwr.xml文件中的create元素的结构如下:
<allow>
<create creator="..." javascript="..." scope="...">
<param name="..." value="..."/>
<auth method="..." role="..."/>
<exclude method="..."/>
<include method="..."/>
</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中。
<create creator="new" javascript="Fred">
<param name="class" value="com.example.Fred"/>
<include method="setWibble"/>
</create>
对于加入到create元素中的类的所有方法都是默认可见的。
auth元素 允许你指定一个J2EE的角色作为将来的访问控制检查:
<create creator="new" javascript="Fred">
<param name="class" value="com.example.Fred"/>
<auth method="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中存放的是什么类型。
public class Check
{
public void setLotteryResults(List nos)
{
...
}
}
signatures段允许我们暗示DWR应该用什么类型去处理。格式对以了解JDK5的泛型的人来说很容易理解。
<signatures>
<![CDATA[
import java.util.List;
import com.example.Check;
Check.setLotteryResults(List<Integer> nos);
]]>
</signatures>
DWR中又一个解析器专门来做这件事,所以即便你的环境时JDK1.3 DWR也能正常工作。
解析规则基本上会和你预想规则的一样(有两个例外),所以java.lang下面的类型会被默认import。
第一个是DWR1.0中解析器的bug,某些环境下不能返回正确类型。所以你也不用管它了。
第二个是这个解析器时"阳光(sunny day)"解析器。就是说它非常宽松,不想编译器那样严格的保证你一定正确。所以有时它也会允许你丢失import:
<signatures>
<![CDATA[
import java.util.List;
Check.setLotteryResults(List<Integer>);
]]>
</signatures>
将来的DWR版本会使用一个更正式的解析器,这个编译器会基于官方Java定义,所以你最好不要使用太多这个不严格的东西。
signatures段只是用来确定泛型参数中的类型参数。DWR会自己使用反射机制或者运行时类型确定类型,或者假设它是一个String类型。所以:
不需要signatures - 没有泛型参数:
public void method(String p);
public void method(String[] p);
需要signatures - DWR不能通过反射确定:
public void method(List<Date> p);
public void method(Map<String, WibbleBean> p);
不需要signatures - DWR能正确的猜出:
public void method(List<String> p);
public void method(Map<String, String> p);
不需要signatures - DWR可以通过运行时类型确定:
public List<Date> method(String p);
没有必要让Javascript中的所有对象的key都是String类型 - 你可以使用其他类型作为key。但是他们在使用之前会被转换成String类型。DWR1.x用Javascript的特性把key转换成String。DWR2.0可能会用toString()方法,在服务段进行这一转换。
engine.js Functions
engine.js对DWR非常重要,因为它是用来转换来至动态生成的接口的javascript函数调用的,所以只要用到DWR的地方就需要它。
The engine.js file
每一个页面都需要下面这些语句来引入主DWR引擎。
<script type='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页面时的动作(通常表示超时) |
调用处理器(Call Handler) (注册到单独调用上的,而不是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("it worked"); },
errorHandler:function(message) { alert("it broke"); },
timeout:1000
});
如果Remote.method()调用超过了1分钟还没有返回,"it broke"消息就会被显示。
DWREngine.setPreHook(function) 和 DWREngine.setPostHook(function) 。
如果你想在DWR调用之前出现一个提示,你可以设置pre-hook函数。它将会被调用,但是没有参数传递到这个函数。当你希望让一些按钮在调用期间变灰来防止被再次使用,这一功能将会很有用。
post-hook用来和pre-hook一起使用来逆转pre-hook产生的做的一些改变。
一个使用pre和post hook的例子就是 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()可以操作select list。
要修改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以后,它仍然保持运来的选择,你要像下面这样做:
var sel = DWRUtil.getValue(id);
DWRUtil.removeAllOptions(id);
DWRUtil.addOptions(id, ...);
DWRUtil.setValue(id, sel);
如果你想加入一个初始的"Please select..." 选项那么你可以直接加入下面的语句:
DWRUtil.addOptions(id, \["Please select ..."]);
然后再下面紧接着加入你真正的选项数据。
数组: 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行不变,并且可以防止Internet Explorer的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函数。
你可以写一些像这样的伪代码:
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")
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修复了这个差异。如果你需要一个同表单元素中按回车相同的特性,你可以用这样代码实现:
<input type="text" οnkeypress="DWRUtil.onReturn(event, submitFunction)"/>
<input type="button" οnclick="submitFunction()"/>
你也可以使用onkeypress事件或者onkeydown事件,他们做同样的事情。
一般来说DWR不是一个Javascript类库,所以它应该试图满足这个需求。不管怎样,这是在使用Ajax过程中一个很有用函数。
这个函数的工作原理是onSubmit()事件只存在于<FORM ...>元素上。
选择一个输入框中的一定范围的文字。
你可能为了实现类似"Google suggest"类型的功能而需要选择输入框中的一定范围的文字,但是不同浏览器间选择的模型不一样。这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风格的加载信息。所有演示页面(dynamic text, selection lists, live tables, live forms, dynamic validation 和 address entry)都使用了GMail风格的加载消息。
这个方法将来可能被废弃,因为这个实现实在太专断了。为什么是红色,为什么在右上角,等等。唯一的真正答案就是:抄袭GMail。这里的建议是以本页面中的代码为模板,根据你的需求自定义。
你必须在页面加载以后调用这个方法(例如,不要在onload()事件触发之前调用),因为它要创建一个隐藏的div来容纳消息。
最简单的做法时在onload事件中调用DWRUtil.useLoadingMessage,像这样:
<head>
<script>
function init() {
DWRUtil.useLoadingMessage();
}
</script>
...
</head>
<body οnlοad="init();">
...
可能有些情况下你是不能容易的编辑header和body标签(如果你在使用CMS,这很正常),在这样的情况下你可以这样做:
<script>
function init() {
DWRUtil.useLoadingMessage();
}
if (window.addEventListener) {
window.addEventListener("load", init, false);
}
else if (window.attachEvent) {
window.attachEvent("onload", init);
}
else {
window.onload = init;
}
</script>
下面这些是这个函数的代码,它对于你要实现自己的加载消息很有用。这个函数的主要内容是动态创建一个div(id是disabledZone)来容纳消息。重要的代码是当远程调用时使它显示和隐藏:
DWREngine.setPreHook(function() {
$('disabledZone').style.visibility = 'visible';
});
DWREngine.setPostHook(function() {
$('disabledZone').style.visibility = 'hidden';
});
This is fairly simple and makes it quite easy to implement your own "loading" message.
function useLoadingMessage(message) {
var loadingMessage;
if (message) loadingMessage = message;
else loadingMessage = "Loading";
DWREngine.setPreHook(function() {
var disabledZone = $('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);
var messageZone = 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);
var text = document.createTextNode(loadingMessage);
messageZone.appendChild(text);
}
else {
$('messageZone').innerHTML = loadingMessage;
disabledZone.style.visibility = 'visible';
}
});
DWREngine.setPostHook(function() {
$('disabledZone').style.visibility = 'hidden';
});
}
下面的做法能简单的使用有加载消息图片:
function useLoadingImage(imageSrc) {
var loadingImage;
if (imageSrc) loadingImage = imageSrc;
else loadingImage = "ajax-loader.gif";
DWREngine.setPreHook(function() {
var disabledImageZone = $('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%";
var imageZone = 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 (var i = 0; i < obj.childNodes.length; i++) {
var childObj = obj.childNodes [i];
if (childObj.nodeValue == null) {
var onclickHandler = childObj.getAttribute('onclick');
if (onclickHandler != null) {
childObj.removeAttribute('onclick');
// If using prototype:
// Event.observe(childObj, 'click', new Function(onclickHandler));
// Otherwise (but watch out for memory leaks):
if (element.attachEvent) {
element.attachEvent("onclick", onclickHandler);
}
else {
element.addEventListener("click", onclickHandler, useCapture);
}
}
DWREngine._fixExplorerEvents(childObj);
}
}
传递额外的数据到callback函数
通常我们需要传递额外的数据到callback函数,但是因为所有的回调函数都只有一个参数(远程方法的返回结果),这就需要一些小技巧了。
解决方案就是使用Javascript的闭包特性。
例如,你的回调函数原本需要像这个样子:
function callbackFunc(dataFromServer, dataFromBrowser) {
// 用dataFromServer和dataFromBrowser做些事情......
}
那么你可以像这个组织你的函数:
var dataFromBrowser = ...;
// 定义一个闭包函数来存储dataFromBrowser的引用,并调用dataFromServer
var callbackProxy = function(dataFromServer) {
callbackFunc(dataFromServer, dataFromBrowser);
};
var callMetaData = { callback:callbackProxy };
Remote.method(params, callMetaData);
(调用元数据在脚本介绍中有解释)
换句话说,现在你作为callback函数传递过来的不是一个真正的callback,他只是一个做为代理的闭包,用来传递客户端的数据。
你可以用更简介的形式:
var dataFromBrowser = ...;
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类:
public class Remote {
public String getData() {
throw new NullPointerException("message");
}
}
那么在Javascript中我们加入下面这些:
function eh(msg) {
alert(msg);
}
{
DWREngine.setErrorHandler(eh);
Remote.getData(function(data) { alert(data); });
结果会通过eh()错误处理器调用alert窗口的,显示消息 – 例如调用异常的getMessage()得到的消息。
我们可以把整个异常传地到Javascript中。如果在dwr.xml中加入转换异常本身的能力:
<convert converter="bean" match="my.special.FunkyException"/>
在这里例子中FunkyException被指定,因为它不仅仅包括一个消息,它还包括一些关于异常的额外数据。例如,SQLException包含错误号,SAX异常包含错误的行和列等等。所以我们可以把上面的例如改为:
public class Remote {
public String getData() {
Date when = new Date();
throw new FunkyException("message", when);
// FunkyException有一个getWhen()方法
}
}
然后在Javascript中是这样的:
function eh(msg, ex) {
alert(msg + ", date=" + ex.when);
}
DWREngine.setErrorHandler(eh);
Remote.getData(function(data) { alert(data); });
结果会是一个eh()错误处理器调用的alert框,上面有这些信息:"message, date=Mon Jan 01 2008 10:00:00 GMT+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>
...
<create creator="spring" javascript="Fred">
<param name="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。如果你知道还有更好的文档,请告知我。
Rob Sanheim 指出还有一个能深入了解ContextLoaderListener的文档。
如果你要在dwr.xml中指定使用哪些bean,你可以使用location*参数。你可以指定任意多个文件,只要参数以location开始并且唯一即可。例如:location-1, location-2。这些location被用做Spring的ClassPathXmlApplicationContext的参数:
<allow>
...
<create creator="spring" javascript="Fred">
<param name="beanName" value="Shiela"/>
<param name="location" value="beans.xml"/>
</create>
</allow>
SpringCreator有一个静态方法 setOverrideBeanFactory(BeanFactory) 用来通过编程的方式直接设置BeanFactory。
Bram Smeets写了一个有意思的blog,教你配置DWR使用beans.xml代替WEB-INF/web.xml。
我也对于如何在beans.xml中指定dwr.xml很感兴趣,尽管这看上去有些Spring传染病的感觉。有人知道如何实现吗?请加入邮件列表并告诉大家。
WebWork支持在DWR2.0m3以后才有。
要可以通过DWR调用WW的Action,要做两件事。
你必须在dwr的配置文件中加入这样的配置:
<create creator="none" javascript="DWRAction">
<param name="class" value="org.directwebremoting.webwork.DWRAction"/>
<include method="execute"/>
</create>
<convert converter="bean" match="org.directwebremoting.webwork.ActionDefinition">
<param name="include" value="namespace,action,method,executeResult" />
</convert>
<convert converter="bean" match="org.directwebremoting.webwork.AjaxResult"/>
这样你AjaxWebWork Action调用返回一个action实例(而不是文字)。然后你必须包括action对象的转换器定义(package级别或单独action)。
<convert converter="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对象. 必须指定下面的内容:
o namespace: xwork.xml中action的名称空间
o action: xwork.xml中action的名字
o executeResult: true|false (是否执行action的结果, 如果false直接返回action实例)
例如:
o DWRActionUtil.execute({
o namespace:'/ajax',
o action:'TestJS',
o executeResult:'true'
}, 'data', doOnJSResult, "stream...");
params 必须是这些:
· emptyParams: 传递{}忽略任何参数。
例子:
DWRActionUtil.execute('/ajax/TestFM', {}, doOnJSResult, "stream...");
· fieldId: 被转换为action调用参数的字段的id。
例子:
· <input id="mytext" name="mytext" value="some value" 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/post Action处理器,在web.xml中的一个context-wide初始化参数(dwrActionProcessor)。处理器必须实现org.directwebremoting.webwork.IDWRActionProcessor 接口。这个处理器将会在action之前和之后被调用,所以你可以做一些预处理或改变结果。
DWR包括两个JSF的扩展点,一个创造器和一个ServletFilter。
DWR1.1中有一个体验版的JsfCreator。你可以哉dwr.xml中这样使用:
<allow>
...
<create creator="jsf" javascript="ScriptName">
<param name="managedBeanName" value="beanName"/>
<param name="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>
...
<create creator="struts" javascript="ScriptName">
<param name="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衍生于它)的排除某些属性的功能:
<param name="exclude" value="propertyToExclude1, propertyToExclude2"/>
HibernateBeanConverter会尝试不去读取没有初始化的属性。如果你只是想读取所有的东西那么应该使用BeanConverter。
建议使用Hibernate3,实际上Hibernate2一下的情况,你会发现你得到的都是空的Bean。
Session管理
如果你使用Hibernate对象,你需要知道每一个DWR请求都是一个新的Servlet请求,所以你需要保证为每个请求打开一个Hiberante的Session。
如果你用Spring,那么可以很方便的使用Spring里面的OpenSessionInViewFilter,它可以保证为每个请求打开一个Hiberante的Session。类似的解决方案在其它Framework中也存在。
如果你需要读取其他web应用程序生成的页面,并返回到Javascript中。非常简单。只要在你的Java类里面包括下面这写代码:
public String getInclude() throws ServletException, IOException
{
return WebContextFactory.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可以整合Acegi security framework。更多的信息见整合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 | 支持Mozilla 1.7以及基于它的浏览器,尽管我们当前只在Mozilla 1.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
· Sun ONE
· Glashfish
1.0版中我们知道两个环境可能有一些问题。当应用服务器的前端被web服务器代理时,web服务器可能重写URL,这样应用服务器可能就不认识这个URL,这时DWR可能出问题。另外DWR运行在Vignette Portal里也可能出问题。
在其他的测试过的环境里DWR应该是没什么问题的。