教你如何使用Dwr
如何开始用DWR
有两种方法开始DWR,简单的方式是下载WAR文件然后看看。但是这不能帮你知道如何轻松的把DWR整合到你的web应用中,所以还是推荐你按照下面的3个步骤做:
1. 安装DWR的Jar包
下载dwr.jar文件。把它放到你的webapp的WEB-INF/lib目录下。那里可能已经有很多其他的jar文件了。
2. 编辑配置文件
需要把下面的代码加到WEB-INF/web.xml文件中。<servlet>那部分需要和其他的<servlet>在一起,<servlet-mapping>部分也一样。
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
在WEB-INF目录下的web.xml旁边创建一个dwr.xml文件。可以从最简单的配置开始:
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
"http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
<allow>
<create creator="new" javascript="JDate">
<param name="class" value="java.util.Date"/>
</create>
<create creator="new" javascript="Demo">
<param name="class" value="your.java.Bean"/>
</create>
</allow>
</dwr>
DWR配置文件定义了那些DWR会创建提供远程调用的Javascript类。在上面的例子中我们定义了两个类来提供远程调用,并为其提供的Javascript类的名字。
在上面我们使用了new创建器,它会调用没有参数的构造函数来创建实例,但是所有JavaBean必须有这一构造函数。还要注意DWR有一些限制:
· 不要出现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/ 页面,点击你的类。查看源码,找到执行方法的那几行,把那些文字粘贴到你的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
XML解析错误
在刚开始用DWR的时候经常遇到的一个错误就是XML解析错误。其实这和DWR没有多大关系,主要是因为Tomcat里面自带的Xerces的问题,要不是该有的时候没有,要不是不该有的时候有了。
· JDK 1.3自身没有XML解析器,所以你需要xercesImpl.jar和xml-apis.jar.
· JDK 1.4.0 和 JDK 1.4.1 虽然有了XML解析器,但是有很多bug,所以你还是需要把xercesImpl.jar放到tomcat\common\endorsed目录下。
· JDK 1.4.2和JDK 5自带的XML解析器工作的很好,你就不需要再加其他的了。
另外要提的一点是,不同版本的Tomcat需要的XML解析器不一样。所以要注意检查它和JDK的版本兼容性。
用BEA Weblogic的Classpath问题
Weblogic 8.1(有可能其他版本同样)可能找不到DWR的类。
这大多出现在dwr.jar放在APP-INF目录下(APP_INF/lib)的情况。在这种情况下DWR依然可以工作,例如debug页面可以看见,但是DWR找不到你的类。
解决办法是把dwr.jar放到WEB-INF/lib目录下。
没有cookies的情况下用DWR
当不能用cookies时,servlet规范通过URL重写来支持HttpSession。DWR 2.x通过它生成的URL来支持这项功能。但是DWR 1.x没有这个功能。你可以通过以下办法让DWR 1.x 也支持cookies:
· 从dwr.jar中提取engine.js,保存到你的文件系统中,就像jsp文件一样.
· 修改"DWREngine._sendData = function(batch)" 方法, 加入一行:
statsInfo += ";jsessionid=" + <%="'"+session.getId()+"'"%>
这样就可以让DWR 1.x支持url重写了。DWR 2+默认支持。
传递额外的数据到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);
}
});
服务器性能优化
CPU瓶颈:经过严格的测试DWR的性能没什么问题。DWR上性能消耗同web服务器和网络比起来可以忽略不计。如果你真的需要提升DWR的性能的话,可以把log级别设置ERROR或FATAL,但是主要还是要看你的编码情况。
Network瓶颈: DWR没有管理你的浏览器缓存的功能,所以它会不断的重复读取DWR的javascript文件。这里有一个简单的解决办法,把javascript文件复制到你的web-app中,这样web服务器就可以更好的利用它了。你也可以考虑把所有的javascript文件合并成一个文件,然后用DOJO的压缩程序处理一个来节省流量。
我们可以做一个补丁,让DWR在web-app启动的时候用时间做为javascript文件的时间戳,但是这个并不十分重要,因为上面的补丁太简单了而且可以压缩合并Javascript文件。
WEB-INF/web.xml 参考手册
在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>
此外还可以加入一些重要和有用的参数。
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 (默认), INFO 和 DEBUG。
多个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,并且以 '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>
使用插件(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.xmldwr.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的内容。
转换器
转换器在客户端和服务器之间转换数据.
下面这些转换器有单独章节介绍
· Array Converter
· Bean and Object Converters
· Collection Converter
· Enum Converter
· DOM Objects
· Hibernate整合
· 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转换器
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,它有可能就是你要的。
The Creators – 创造器
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' 创造器
'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() 。
dwr.xml中的签名(Signatures)
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 Functionsengine.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
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中叫'verb'
rpcType
2.0
2.0
2.0
选择是使用xhr, iframe或者script-tag来实现远程调用. 1.x中叫'method'
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开始服务端异常通过'exceptionHandler'处理
warningHandler
1.0
2.0
2.0
当因为浏览器的bug引起问题时的动作,所以默认这个设置为null(关闭)
textHtmlHandler
2.0
2.0
2.0
当得到不正常的text/html页面时的动作(通常表示超时)
调用处理器(Call Handler) (注册到单独调用上的,而不是batch中的所有调用)Option
Global
Batch
Call
Summary
callback
-
-
1.0
调用成功以后的要执行的回调函数,应该只有一个参数:远程调用得到的数据
exceptionHandler
-
-
2.0
远程调用失败的动作,一般是服务端异常或者数据转换问题。
Hooks (一个batch中可以注册多个hook)Option
Global
Batch
Call
Summary
preHook
1.0
1.1
1.1
远程调用前执行的函数
postHook
1.0
1.1
1.1
远程调用后执行的函数
全局选项(在单次调用或者批量调用中不可用)Option
Global
Batch
Call
Summary
ordered
1.0
-
-
DWR是否支持顺序调用
pollType
2.0
-
-
选择xhr或者iframe的反转Ajax
reverseAjax
2.0
-
-
是否查找inbound调用
废弃的选项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就应该 总是 为每一个请求提供响应。
Call Batching
你可以使用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"消息就会被显示。
远程调用Hooks
DWREngine.setPreHook(function) 和 DWREngine.setPostHook(function) 。
如果你想在DWR调用之前出现一个提示,你可以设置pre-hook函数。它将会被调用,但是没有参数传递到这个函数。当你希望让一些按钮在调用期间变灰来防止被再次使用,这一功能将会很有用。
post-hook用来和pre-hook一起使用来逆转pre-hook产生的做的一些改变。
一个使用pre和post hook的例子就是 DWRUtil.useLoadingMessage() 函数。
远程调用选项
DWR有一些选项用来控制远程调用的处理方式。method和verb对于用户应该时透明的,但是不同的浏览器效果的可能不一样。一般来说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)
这个选项允许你选择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
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找个替代品。
util.js: 生成列表
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有5种模式
数组: 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列表。
util.js: 生成Table
DWR通过这两个函数来帮你操作table: DWRUtil.addRows() 和 DWRUtil.removeAllRows() 。这个函数的第一个参数都是table、tbody、thead、tfoot的id。一般来说最好使用tbody,因为这样可以保持你的header和footer行不变,并且可以防止Internet Explorer的bug。
DWRUtil.removeAllRows()
语法:
DWRUtil.removeAllRows(id);
描述:
通过id删除table中所有行。
参数:
· id: table元素的id(最好是tbody元素的id)
DWRUtil.addRows()
语法:
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")
DWRUtil.getText(id)
getText(id)和getValue(id)很相似。出了它是为select列表设计的。你可能需要取得显示的文字,而不是当前选项的值。
DWRUtil.getValue(id)
DWRUtil.getValue(id)是 setValue()对应的"读版本"。它可以从HTML元素中取出其中的值,而你不用管这个元素是select列表还是一个div。
这个函数能操作大多数HTML元素包括select(去处当前选项的值而不是文字)、input元素(包括textarea)、div和span。
DWRUtil.getValues()
getValues()和getValue()非常相似,除了输入的是包含name/value对的javascript对象。name是HTML元素的ID,value会被更改为这些ID对象元素的内容。这个函数不会返回对象,它只更改传递给它的值。
基于Form的getValues()
从DWR1.1开始getValues()可以传入一个HTML元素(一个DOM对象或者id字符串),然后从它生成一个reply对象。
DWRUtil.onReturn
当按下return键时,得到通知。
当表单中有input元素,触发return键会导致表单被提交。当使用Ajax时,这往往不是你想要的。而通常你需要的触发一些Javscript。
不幸的是不同的浏览器处理这个事件的方式不一样。所以DWRUtil.onReturn修复了这个差异。如果你需要一个同表单元素中按回车相同的特性,你可以用这样代码实现:
<input type="text" onkeypress="DWRUtil.onReturn(event, submitFunction)"/>
<input type="button" onclick="submitFunction()"/>
你也可以使用onkeypress事件或者onkeydown事件,他们做同样的事情。
一般来说DWR不是一个Javascript类库,所以它应该试图满足这个需求。不管怎样,这是在使用Ajax过程中一个很有用函数。
onSubmit
这个函数的工作原理是onSubmit()事件只存在于<FORM ...>元素上。
DWRUtil.selectRange
选择一个输入框中的一定范围的文字。
你可能为了实现类似"Google suggest"类型的功能而需要选择输入框中的一定范围的文字,但是不同浏览器间选择的模型不一样。这DWRUtil函数可以帮你实现。
DWRUtil.setValue(id, value)
DWRUtil.setValue(id, value)根据第一个参数中指定的id找到相应元素,并根据第二个参数改变其中的值。
这个函数能操作大多数HTML元素包括select(去处当前选项的值而不是文字)、input元素(包括textarea)、div和span。
DWRUtil.setValues()
setValues()和setValue()非常相似,除了输入的是包含name/value对的javascript对象。name是HTML元素的ID,value是你想要设置给相应的元素的值。
DWRUtil.toDescriptiveString
DWRUtil.toDescriptiveString()函数比默认的toString()更好。第一个参数是要调试的对象,第二个参数是可选的,用来指定内容深入的层次:
· 0: 单行调试
· 1: 多行调试,但不深入到子对象。
· 2: 多行调试,深入到第二层子对象
以此类推。一般调试到第二级是最佳的。
还有第三个参数,定义初始缩进。这个函数不应该被用于调式程序之外,因为以后可能会有变化。
DWRUtil.useLoadingMessage
设置一个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 onload="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对象会包含异常在服务端的所有属性,但是异常栈信息没有。
DWR和Spring
让DWR和Spring一起工作的步骤
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的支持不会产生任何影响到。
The SpringCreator
这个创造器会查找spring的中配置的Bean,用Spring去创建它们。如果你已经在使用Spring,那么这个创造器会非常有用。
你可以通过下面的方式来创建远程调用的Bean:
<allow>
...
<create creator="spring" javascript="Fred">
<param name="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。如果你知道还有更好的文档,请告知我。
Rob Sanheim 指出还有一个能深入了解ContextLoaderListener的文档。
使用location*参数
如果你要在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>
直接设置BeanFactory
SpringCreator有一个静态方法 setOverrideBeanFactory(BeanFactory) 用来通过编程的方式直接设置BeanFactory。
配置DWR和Spring
Bram Smeets写了一个有意思的blog,教你配置DWR使用beans.xml代替WEB-INF/web.xml。
我也对于如何在beans.xml中指定dwr.xml很感兴趣,尽管这看上去有些Spring传染病的感觉。有人知道如何实现吗?请加入邮件列表并告诉大家。
通过DWR来调用WebWork的Action
WebWork支持在DWR2.0m3以后才有。
要可以通过DWR调用WW的Action,要做两件事。
步骤1: 配置dwr.xml
你必须在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>.*"/>
步骤2: 在JSP中导入脚本
下面这些代码开启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之前和之后被调用,所以你可以做一些预处理或改变结果。
JSF 整合
DWR包括两个JSF的扩展点,一个创造器和一个ServletFilter。
'jsf'创造器
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。
The Servlet Filter
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放在一起。
整合Struts
DWR几乎可以和任何Framework一起工作。这个网站(DWR的官方网站)就是这一点的有力证明,因为它是在Drupal(PHP)中使用DWR。
DWR和Struts整合有两个层次。最基础的层次就是同时使用两个Framework,这是非常容易的,但是这样就不允许在DWR和Struts之间共享Action了。
DWR可以调用任何方法,所以没有理由不让你从DWR调用Struts的Action,除非你不想这样用。ActionForm的内容是什么,当返回ActionForward时DWR怎么做?
一个比较好方法是重构你想调用的Action,提取出Action的逻辑。DWR和你的Action就可以同时调用相同的方法了。
The 'struts' 创造器
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中也存在。
从其他的URL读取数据
如果你需要读取其他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带来的最大好处
很值得对比一下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的源码,保证它是在正确的工作。这些代码已经被很多人检查过了,但多双眼睛总是有好处的。
整合Acegi
DWR可以整合Acegi security framework。更多的信息见整合DWR和Acegi.
Disclaimer
尽管我们很谨慎的对待DWR的安全,但是我们不能声称DWR是绝对安全的,如果这样做是很愚蠢的。你的web站点的安全是你的责任。DWR可以通过在开源授权下提供源码来帮你,你可以自己研究,我们鼓励你这样做,并且很多已经这样做了。
浏览器支持
我们希望能测试所有的浏览器,所以如果你可以提供下面列表中任何浏览器的更多信息,或没有列出的浏览器的信息,请加入DWR用户邮件列表。
Firefox
版本
状态
1.0
支持:没发现问题
0.7
很少人会用1.0以前的版本,所以尽管我们相信DWR从0.7开始就可以在Firefox上工作,但是我们没有计划在Firefox上测试1.0以前的版本
Internet Explorer
版本
状态
6.0 (Win)
支持:没发现问题。
5.5 (Win)
相信可以工作,但是应该做更多的测试。
5.0.2 (Win)
大多功能没问题,但是有报告说丢失信息。
5.2 (OSX)
不能工作,并且没有计划支持Mac上的IE。
Netscape或其他的基于Mozilla的浏览器
版本
状态
1.7
支持Mozilla 1.7以及基于它的浏览器,尽管我们当前只在Mozilla 1.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
· Sun ONE
· Glashfish
1.0版中我们知道两个环境可能有一些问题。当应用服务器的前端被web服务器代理时,web服务器可能重写URL,这样应用服务器可能就不认识这个URL,这时DWR可能出问题。另外DWR运行在Vignette Portal里也可能出问题。
在其他的测试过的环境里DWR应该是没什么问题的。