<script type="text/javascript"> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>
DWR(Direct Web Remoting)是一个开放源码的使用 Apache 许可协议的解决方案,它包含服务器端 Java 库、一个 DWR Servlet 以及 JavaScript 库。虽然 DWR 不是 Java 平台上唯一可用的 Ajax-RPC 工具包,但是它是最成熟的,而且提供了许多有用的功能。为什么要使用DWR,我们首先介绍基本AJAX流程,从中可以看到引入DWR会带来什么好处。
1、AJAX的基本介绍
Ajax (Asynchronous JavaScript And XML)描述了一种使用混合了HTML(或XHTML)和层叠样式表作为表达信息,来创建交互式的Web应用的开发技术;文档对象模型(DOM)、JavaScript、动态地显示和与表达信息进行交互;并且,XMLHttpRequest对象与Web服务器异步地交换和处理数据。
使用AJAX,我们可以开发出有很好交互性的B/S程序,同时AJAX局部和异步刷新的特性也大大减小了对服务器和网络的压力。对于非AJAX的B/S程序来说HTML、DOM、JS同样是不可或缺的关键技术,因此引入AJAX技术主要是增加了对于XMLHttpRequest对象的使用。我们使用AJAX来开发一个简单的AJAX程序。
例1:
服务端的TestAjaxServlet:
public void doPost(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException { res.getWriter().write("Getting Message uses AJAX directly!"); }
请将些Servlet配置成ajax/testAjaxServlet.
客户端的使用AJAX去调用服务端的TestAjaxServlet并响应:
function getAjaxMessage() { var http_request = false; if (window.XMLHttpRequest) { // Mozilla, Safari,... http_request = new XMLHttpRequest(); if (http_request.overrideMimeType) { http_request.overrideMimeType("text/html"); } } else { if (window.ActiveXObject) { // IE try { http_request = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { http_request = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { } } } } if (http_request === false) { alert("Cannot create XMLHTTP instance"); return false; } http_request.onreadystatechange = function () { document.getElementById(‘MessageSpan’).innerHTML = http_request.responseText; }; http_request.open("POST", “ajax/testAjaxServlet.do“, true); http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http_request.setRequestHeader("Content-length", parameters.length); http_request.setRequestHeader("Connection", "close"); http_request.send(parameters); }
当客户端调用getAjaxMessage()成功后,页面上的“MessageSpan”部分会出面“Getting Message uses AJAX directly!”,表明调用成功,且页面没有整体全部的刷新,在处理没有完成的时候,用户和页面上其它的UI交互并不受影响。
与传统B/S开发方法相比,服务端代码没有受到影响,客户端使用则需要创建XMLHttpRequest对象与服务端进行交互,同时在服务端调用完成后,使用JS回调函数处理服务端返回的数据。AJAX为B/S程序带来了种种好处的同时,也带来了下列问题,一是需要使用XMLHttpRequest对象,二是如果服务端想要返回一个复杂的数据结构时,一般只能返回XML代码,虽然有很多相对应的工具来解析XML,但还是增加了较大的工作量,有没有一种方法或工具,能够封装XMLHttpRequest,屏蔽HTTP协议细节,不会对服务端的代码造成影响,同时能让将服务端返回的复杂数据结构隐式转换为客户端JS程序可以直接使用的数据结构?有,答案就是DWR。
2、DWR框架基本介绍
从最简单的角度来说,DWR 是一个引擎,可以把服务器端 Java 对象的方法公开给 JavaScript 代码。使用 DWR 可以有效地从应用程序代码中把 Ajax 的全部请求-响应循环消除掉。这意味着客户端代码再也不需要直接处理 XMLHttpRequest 对象或者服务器的响应。不再需要编写对象的序列化代码或者使用第三方工具才能把对象变成 XML。甚至不再需要编写 servlet 代码把 Ajax 请求调整成对 Java 域对象的调用。
DWR 是作为 Web 应用程序中的 servlet 部署的。把它看作一个黑盒子,这个 servlet 有两个主要作用:首先,对于公开的每个类,DWR 动态地生成包含在 Web 页面中的 JavaScript。生成的 JavaScript 包含存根函数,代表 Java 类上的对应方法并在幕后执行 XMLHttpRequest。这些请求被发送给 DWR,这时它的第二个作用就是把请求翻译成服务器端 Java 对象上的方法调用并把方法的返回值放在 servlet 响应中发送回客户端,编码成 JavaScript。DWR的标准流程如下图所示:
页面触发eventHandler()事件,事件内部调用了AjaxService.getOptions方法,当调用完成后,利用服务端返回的数据用客户端的populateList()方法进行数据展现。
我们通过一个简单的DWR示例来说明如何使用DWR。
为了使用DWR,需要将DWR的jar文件拷入Web应用的WEB-INF/lib目录中(可在http://getahead.org/dwr下载),在web.xml中增加一个servlet声明,并创建DWR的配置文件。
服务端的配置:
<servlet> <servlet-name>dwr-invoker</servlet-name> <display-name>DWR Servlet</display-name> <description>Direct Web Remoter Servlet</description> <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中创建文件dwr.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd"> <dwr> <allow> </allow> </dwr>
服务端的TestDWR:
public String getMessage()
{
return "Getting Message uses DWR!";
}
在dwr.xml的<allow></allow>中增加如下内容:
<create creator="new" javascript="TestDWR"> <param name="class" value="ajax.TestDWR"/> </create>
至此,服务端的配置已经完成,如果有新的功能增加,只需要对在dwr.xml中进行相应的配置即可。
客户端的配置:
如果页面要使用DWR,需要将下面三行引入:
<script src='/[YOUR-WEBAPP]/dwr/interface/[YOUR-SCRIPT].js'></script>
<script src='/[YOUR-WEBAPP]/dwr/engine.js'></script>
在页面中增加如下代码定义JS函数:
<script> function getDWRMessage () { TestDWR. getMessage (showMessage); } function showMessage(data) { document.getElementById(‘MessageSpan’).innerHTML = data; } </script>
当客户端调用getDWRMessage ()成功后,页面上的“MessageSpan”部分会出面"Getting Message uses DWR!",表明DWR调用成功,且页面没有整体全部的刷新,在处理没有完成的时候,用户和页面上其它的UI交互并不受影响。
对例1和例2进行对比,我们可以发现使用DWR开发B/S应用时,DWR为我们屏蔽了HTTP协议的细节,不再需要使用XMLHttpRequest对象来与服务器进行交互,解决了我们在编写AJAX应用时一个较大的问题,但在例2中我们中只返回了一个简单的字符串,如果我们希望返回一个较复杂的数据结构,如一个对象数组,DWR又如何解决这个问题呢?
例3:
定义一个Java Bean:
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
在dwr.xml的<arrow></arrow>间增加如下内容:
<convert converter="bean" match="ajax.User"> </convert>
在现有的服务端代码中增加一个方法:
public ArrayList getUserList(){
ArrayList userList = new ArrayList();
User userA = new User();
userA.setUsername("John");
userA.setPassword("123456");
userList.add("userA");
User userB = new User();
userB.setUsername("Tom");
userB.setPassword("123456");
userList.add("userB");
return userList;
}
在客户端,我们增加一个JS函数来调用服务端的getUserList方法
Function getUserListFromServer(){ Util.getUserList({callback:function(dataFromServer){ for(var i = 0; i < dataFromServer.length; i++){ alert(dataFromServer[i].username); } }}); }
当触发页面的getUserListFromServer方法后,“John”, “Tom”顺序显示,这样,我们直接使用服务端定义的一个对像数组,就像js中定义的一样,不需要我们手工进行转换。
3、DWR框架的缺点
通过上面的示例程序,我们可以看到使用DWR开发B/S程序给我们带来了很大的便利,但任何技术都有不足之处,对这些不足之处的充分了解,有助于我们更好的理解DWR的适用范围,发挥它的优点。
同任何 RPC 机制一样,在 DWR 中,可能很容易忘记对于远程对象进行的每个调用都要比本地函数调用昂贵得多。DWR 在隐藏 Ajax 的机械性方面做得很好,但是重要的是要记住网络并不是透明的 —— 进行 DWR 调用会有延迟,我们不能因为某些实现上的问题,将完全可由客户端完成的功能转交服务端完成,这样会导致服务器和网络增加了不必要的开销。
DWR在一定程度上造成了客户端和服务端形成耦合,因为客户端“直接”调用服务端的方法,因此接口的变更会对两者都造成影响。
在例1的AJAX程序中,虽然是用XMLHttpRequest对像来发起对服务器的请求,但对服务器来说与普通的页面提交没有区别,因DWR却是直接调用了Server端的函数,因为对于标准AJAX来说非常简单的功能对DWR来说却无法完成,以基于Struts的B/S应用为例,当客户端提交一个请求时,框架会首先获取此请求,进行相应的预处理,创建准备调用的Action所需的各种数据对像后,最终才会调用Action中对应的方法,但是DWR调用时,Struts框架被绕过了,Action所需的各种数据对像无法生成,因此也就无法调用。
4、DWR框加的高级话题
A.配置服务端方法的可调用范围
在以上的2个DWR示例中,我们配置了两个JAVA类,将它们的所有属性和方法都暴露给了客户端,为了提高安全性,我们通常在dwr.xml中应该只配置那些客户端需要使用的方法和属性。DWR支持对dwr.xml更细粒度的配置来提高安全性,我们先看一下与配置有关的两个元素:
create 元素
create 元素告诉 DWR 应当公开给 Ajax 请求的服务器端类,并定义 DWR 应当如何获得要进行远程的类的实例。这里的 creator 属性被设置为值 new,这意味着 DWR 应当调用类的默认构造函数来获得实例。其他的可能有:通过代码段用 Bean 脚本框架(Bean Scripting Framework,BSF)创建实例,或者通过与 IOC 容器 Spring 进行集成来获得实例。默认情况下,到 DWR 的 Ajax 请求会调用 creator,实例化的对象处于页面范围内,因此请求完成之后就不再可用。
create 的 javascript 属性指定从 JavaScript 代码访问对象时使用的名称。嵌套在 create 元素内的 param 元素指定 creator 要创建的 Java 类。最后,include 元素指定应当公开的方法的名称。显式地说明要公开的方法是避免偶然间允许访问有害功能的良好实践 —— 如果漏了这个元素,类的所有方法都会公开给远程调用。反过来,可以用 exclude 元素指定那些想防止被访问的方法。
convert 元素
convert 元素的作用是告诉 DWR 在服务器端 Java 对象表示和序列化的 JavaScript 之间如何转换数据类型。DWR 自动地在 Java 和 JavaScript 表示之间调整简单数据类型。这些类型包括 Java 原生类型和它们各自的类表示,还有 String、Date、数组和集合类型。DWR 也能把 JavaBean 转换成 JavaScript 表示,但是出于安全性的原因,做这件事要求显式的配置。
<dwr> <allow> <create creator="new" javascript="TestDWR"> <param name="class" value="ajax.TestDWR"/> <include method="getUserList"/> <exclude method="getDWRMessage"/> </create> <convert converter="bean" match="ajax.User"> <param name="include" value="username"/> <param name="exclude" value="password"/> </convert> </allow> </dwr>
B.DWR分模块配置
一般来说,你只需要一个dwr.xml文件,并且放置在默认的位置:WEB-INF/dwr.xml。如果有大量的远程调用类,则可以将dwr.xml分成多个文件。 则在每web .xml中可以这样配置:
<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>
C.向服务端方法传入特殊对像
虽然我们不能直接调用Action,但是DWR还是能我们生成某些特殊对像HttpServletRequest,HttpServletResponse,HttpSession,ServletContext,ServletConfig.
,自动将它们传入服务端方法中,我们可以直接使用。
Java端定义方法: saveOrder(String id, String name, HttpSession session)
Javascript调用: OrderService.saveOrder(jsID,jsName,callBack)
这里不需要传入session这个参数,DWR会自动传入,在Java端可以直接使用.
D.传递多个参数到回调函数
var dataFromBrowser = ...; 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); } });
E.DWR批量调用
在 DWR 中,可以在一个 HTTP 请求中向服务器发送多个远程调用。调用 DWREngine.beginBatch() 告诉 DWR 不要直接分派后续的远程调用,而是把它们组合到一个批请求中。DWREngine.endBatch() 调用则把批请求发送到服务器。远程调用在服务器端顺序执行,然后调用每个 JavaScript 回调。
批处理在两方面有助于降低延迟:第一,避免了为每个调用创建 XMLHttpRequest 对象并建立相关的 HTTP 连接的开销。第二,在生产环境中,Web 服务器不必处理过多的并发 HTTP 请求,改进了响应时间。