2008 年 8 月 29 日
您能很快地说出多少 Java™ Web 开发框架、库和工具箱?没错,数量太多,以至于很难弄清楚它们各自的功能以及哪个功能可以真正帮助您解决问题。但是,如果您从事的是 Ajax 开发,那么您必须要知道这个库:Direct Web Remoting (DWR)。它利用 Java 语言和 Java Web 技术大大地简化了 Ajax 开发,并为如何无缝地将 Ajax 集成到 Java Web 应用程序设立了标准。实际上,DWR 加入了 Dojo Foundation,后者集合了许多流行的开源 Ajax 技术。在本文中,了解使用 DWR 轻松开发 Ajax。
本文是包含 3 个部分的系列文章的第 3 部分,也是最后一个部分,这个系列文章介绍了创建 Ajax 支持的应用程序所需的流行 JavaScript 库。在 第 1 部分 ,您了解了如何使用 Prototype 库构建一个用来管理歌曲的 Web 应用程序。第 2 部分 讨论了如何使用 Scriptaculous 库构建一个用来管理照片的 Web 应用程序。本文侧重于向您展示如何利用 DWR 简化 Ajax 开发。
|
本 文使用 DWR 2.0 版本。由于使用了泛型和注释,所以示例代码要求 Java 5+。示例应用程序结合使用了 MySQL 5.12 和 Tomcat 6.0.14。当然,您应该能够轻松切换到其他实现。应用程序还使用了 Java Persistence API (JPA) 和 OpenJPA 1.0,分别用于数据访问和应用程序的 JPA 实现。同样,您也应该能够切换到其他 JPA 实现(比如 Hibernate、Kodo 等)。本文使用了 Firefox 的 Firebug 插件,因为该插件是一个很棒的 Ajax 调试工具。在 参考资料 部分可以获得这些工具的相关链接。
Ajax 应用程序看上去有些神秘,幸好开发应用程序的过程还比较直观。对于每个 Ajax 交互,必须在服务器上创建一个端点(从从事 Web 服务的朋友那里借用的术语),并且还要创建客户端代码来调用该端点。此外,还必须创建用于序列化客户机和服务器之间的数据流的所有代码。这些服务器端点可 以是泛型服务,甚至可以是 REST 式的端点。但是,它们的创建通常特定于客户机的需要。有时应该避免紧密耦合,但有时又应该采用。对于后一种情况,DWR 是一种一揽子的解决方案。它允许您将服务器端代码作为 Ajax 端点公开,而所有模板都是自动生成的。现在,让我们通过一个特定的示例来看看 DWR 是如何工作的。
|
这里使用的示例应用程序是一个简单的留言板。数据模型已经大大简化,以便您能把精力放在借助 DWR 的 Ajax 交互。我们先来研究一下应用程序的后端,看看 DWR 如何能层叠于其上以启用应用程序的 Ajax 功能。
|
先为留言板创建一个数据库表。清单 1 给出了创建此表所需的 SQL 脚本。
CREATE TABLE 'messages' ( 'id' int(11) NOT NULL auto_increment, 'title' varchar(40) NOT NULL, 'body' text, 'created_at' timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, 'author' varchar(40) NOT NULL default 'anonymous', PRIMARY KEY ('id') ) ENGINE=InnoDB DEFAULT CHARSET=latin1; |
这里需要特别注意的一点是:需要为留言板上的每个消息提供标 题、主体和作者。SQL 脚本会完成除此之外的其他工作。所显示的脚本是针对 MySQL 的,但稍微调整便可用于其他 RDBMS 的 SQL。对于数据访问,我使用的是 JPA。此表对应的 Java 类如清单 2 所示。
package org.developerworks.msgb; import java.util.Date; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Id; import javax.persistence.GeneratedValue; import static javax.persistence.GenerationType.IDENTITY; import javax.persistence.Column; import org.directwebremoting.annotations.DataTransferObject; @DataTransferObject @Entity @Table(schema="msgb", name = "messages") public class Message { @Id @GeneratedValue(strategy=IDENTITY) private int id; @Column(name="title") private String title; @Column(name="author") private String author; @Column(name="body") private String body; @Column(name="created_at") private Date createdAt; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } } |
这段代码的大部分都是样本文件代码:几个字段和一些 getter 和 setter 方法。而且,大多数注释都只是一些标准的 JPA 注释,它可将 Java 字段影射到数据库列。需要注意其中的一个异常注释,即 @DataTransferObject
。它是一个 DWR 注释,告知 DWR 此类可以自动整理,并作为 Ajax 响应的一部分发送。DWR 包括几个整理 Java 类型的转换器。也可以随意指定 DWR 包括/不包括哪些字段,这同样可以通过使用注释实现。设置好数据后,就可以为应用程序创建服务了。
|
示例应用程序主要完成两个简单的目标:一是列出张贴到留言板上的所有消息,二是允许用户向留言板张贴消息。参见清单 3,它给出了此应用程序的服务代码。
package org.developerworks.msgb; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.directwebremoting.annotations.RemoteMethod; import org.directwebremoting.annotations.RemoteProxy; @RemoteProxy public class MessageService { private EntityManagerFactory factory; private EntityManager em; public MessageService(){ factory = Persistence.createEntityManagerFactory("msgb"); em = factory.createEntityManager(); } @RemoteMethod @SuppressWarnings("unchecked") // thanks type erasure public List<Message> getMessages(){ List<Message> messages = em.createQuery("select m from Message m").getResultList(); return messages; } @RemoteMethod public void saveMessage(Message msg){ em.getTransaction().begin(); em.persist(msg); em.getTransaction().commit(); } } |
这里所示的代码是一些用来查询和创建消息的标准 JPA 代码。同样,这里的注释代码也很有趣。这个类被注释为 RemoteProxy
。这就告诉 DWR 远程客户机可以调用该类的方法。当然,这些远程调用的机制是借助 DWR 的 Ajax。通过 RemoteMethod
注释显式地将该类的每个方法公开给远程客户机。如果不想公开某些方法,省略相应的注释即可。至此我们所写的所有代码都是一些后端代码和注释。要在后端设置 DWR,还需要做另一件事情。
|
DWR 使用 Java servlet 接受和响应 Ajax 请求。这个 servlet 包括在 DWR 库内,因此只需配置 Web 应用程序,以便在应用程序的 web.xml 文件内启用 servlet。清单 4 给出了示例应用程序的 web.xml。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="msgb" version="2.5"> <display-name>MessageBoard</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <servlet> <display-name>DWR Servlet</display-name> <servlet-name>dwr-invoker</servlet-name> <servlet-class> org.directwebremoting.servlet.DwrServlet </servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>classes</param-name> <param-value>org.developerworks.msgb.MessageService, org.developerworks.msgb.Message</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> </web-app> |
上述代码公开了 DWR servlet 并向其发送所有以 /dwr/ 开始的 URL。此外,请注意 init-params
。classes
参数是一个以逗号分隔的列表,它由带有 DWR 注释的所有类组成。注意:如果不喜欢注释,也可以使用 XML 文件来获取相同的信息。这就是在后端需要做的所有工作,现在可以开始在应用程序的前端使用 DWR 了。
|
DWR 使在应用程序的后端启用 Ajax 变得十分容易。需要做的仅是添加一些声明和激活一个 DWR servlet。但前端的情况又如何呢?可以使用大多数 JavaScript 工具箱的某些库。通常,要在 Web 页面内引用这些库,然后阅读有关文档了解如何使用它们。不过这不适用于 DWR。
DWR 会自动生成 JavaScript。所幸的是,借助 DWR 能够轻松了解如何使用该 JavaScript。回顾一下清单 4 中的 web.xml 文件。注意到 init-param
调用的调试了吗?这一设置允许自检 DWR 创建的动态 DWR JavaScript。简单部署 Web 应用程序后转到以下的 URL: http://<root>/<web_app_name>/dwr,如图 1 所示。
该图显示出了用 @RemoteProxy
声明标注的所有 Java 类。单击任何一个类,就会显示相关细节,如图 2 所示。
图 2. MessageService JavaScript 信息
这个界面展示了如何在 Web 页面上引用动态 JavaScript。这里有一个必须包含的核心库(engine.js),以及一个特定于此应用程序的纯动态库(MessageService.js)。这里的一个可选实用工具库提供了许多 helper 函数。
掌握了如何引用 DWR JavaScript 后,还需知道如何使用它。可以使用 MessageBoard 页面测试它,如图 3 所示。
此图展示了 getMessages()
调用的 Ajax 请求的结果。所显示的数据是一个 JavaScript 对象(JSON)的数组。我们知道了 API 将要返回的内容,但如何调用它呢?我们看看测试页面的源代码,如清单 5 所示。
<li> getMessages( ); <input class='ibutton' type='button' οnclick='MessageService.getMessages(reply0);' value='Execute' title='Calls MessageService.getMessages(). View source for details.'/> <script type='text/javascript'> var reply0 = function(data) { if (data != null && typeof data == 'object') alert(dwr.util.toDescriptiveString(data, 2)); else dwr.util.setValue('d0', dwr.util.toDescriptiveString(data, 1)); } </script> <span id='d0' class='reply'></span> </li> <li> saveMessage( <input class='itext' type='text' size='10' value='' id='p10' title='Will be converted to: org.developerworks.msgb.Message'/> ); <input class='ibutton' type='button' οnclick='MessageService.saveMessage (objectEval($("p10").value), reply1);' value='Execute' title='Calls MessageService.saveMessage(). View source for details.'/> <script type='text/javascript'> var reply1 = function(data) { if (data != null && typeof data == 'object') alert(dwr.util. toDescriptiveString(data, 2)); else dwr.util.setValue('d1', dwr.util.toDescriptiveString(data, 1)); } </script> <span id='d1' class='reply'></span> </li> |
因此,比如要调用 getMessages
,则可以使用 MessageService.getMessages(callbackFunction)
。测试该方法时,您将看到传递给 callbackFunction
的参数。现在可以构建 Web 页了,如清单 6 所示。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>The developerWorks Message Board</title> <script type="text/javascript" src="/MessageBoard/dwr/interface/ MessageService.js"></script> <script type="text/javascript" src="/MessageBoard/dwr/ engine.js"></script> <script type="text/javascript" src="/MessageBoard/dwr/ util.js"></script> <script type="text/javascript"> var columns = ["title", "author", "body", "createdAt"]; function loadData(){ MessageService.getMessages(addRows); } // messages should be an array of Message hashes function addRows(messages){ var cellfuncs = []; for each (var prop in columns){ cellfuncs.push(createFunction(prop)); } dwr.util.addRows("messageTableBody", messages, cellfuncs); } function createFunction(prop){ return function(msg) { return msg[prop]; }; } function sendMsg(){ var msg = {}; msg.title = dwr.util.getValue("title"); msg.author = dwr.util.getValue("author"); msg.body = dwr.util.getValue("body"); MessageService.saveMessage(msg); msg.createdAt = Date(); var messages = [msg]; addRows(messages); clearForm(); } function clearForm(){ dwr.util.setValue("title",""); dwr.util.setValue("author",""); dwr.util.setValue("body",""); } </script> <style type="text/css"> label { float: left; text-align: right; margin-right: 15px; width: 100px; } #messageTableHead { font-weight: 900; color:navy; } body { color:MidnightBlue; background-color:PaleTurquoise; margin:20px; padding:0px; font:11px verdana, arial, helvetica, sans-serif; } .pageTitle { margin:0px 0px 15px 0px; padding:0px; font-size:28px; font-weight:900; color:#aaa; } #formDiv{ padding-top:12px; color:Indigo; } </style> </head> <body onLoad="loadData()"> <div class="pageTitle">The developerWorks Message Board</div> <div class="pageContent"> <table id="messageTable" border="1" cellpadding="4"> <thead id="messageTableHead"> <tr> <td>Title</td> <td>Author</td> <td>Message</td> <td>Posted</td> </tr> </thead> <tbody id="messageTableBody"> </tbody> </table> </div> <div id="formDiv"> <form id="messageForm"> <label>Message Title:</label><input type="text" name="title" id="title"/><br/> <label>Your Name:</label><input type="text" name="author" id="author"/><br/> <textarea name="body" cols="80" rows="15" id="body"></textarea><br/> <input type="button" id="msgBtn" value="Add Message" onClick="sendMsg()"/> </form> </div> </body> </html> |
我们分析一下这段代码。首先,它是一个简单的 HTML 文件。它不是 JSP 或 JSF 页面,而是具有一些 JavaScript 和 CSS 的静态 HTML。加载这个页面时,调用 loadData()
函数。该函数使用刚刚介绍过的 MessageService.getMessages()
调用。它传入 addRows
函数,以便处理 DWR 发出的 Ajax 请求的响应。
addRows()
函数接受服务器返回的数据并使用 DWR 提供的一种实用工具函数。该函数是 dwr.util.addRows
,它接受 HTML 表、表头、表脚或表体的 ID,并向它们追加行。它使用了一个函数(此段代码中的 cellfuncs
)数组,其中的每个函数都用来压缩或转换数据,以便将数据放入表的单元格内。因此,此表的第 (i,j) 个元素内的数据将会是 cellfuncs[j](messages[i])
。这是一种将数据影射到表的简单方法。在浏览器内显示此页面,效果类似图 4。
该页使用支持 DWR 的 Ajax 异步加载数据。因此能够显示消息,但如何保存新消息呢?这可通过页面底部的表单来实现。单击 Add Message 时,使用 DWR 的实用方法 dwr.util.getValue
从表单获得数据。此实用工具函数适用于任何 HTML 元素,包括 div
、文本输入、复选框和选择列表。获得数据后,将其放入 JavaScript 对象并调用 MessageService.saveMessage()
。在本例中,由于响应为空,所以没有指定处理函数,而是重用了前面的 addRows
函数,用来向表追加新行。这使 UI 的响应性变得很好。
填写表单并单击 Add Message ,可以测试该页。使用类似 Firebug 的工具可以查看 HTTP 流量。清单 7 给出了 saveMessage
调用发送的数据。
callCount=1 page=/MessageBoard/ httpSessionId= scriptSessionId=B88B0681A9BB674C14786B7DCA3EA6E3153 c0-scriptName=MessageService c0-methodName=saveMessage c0-id=0 c0-e1=string:The%20One%20I%20Love c0-e2=string:Michael c0-e3=string:This%20goes%20out%20to%20the%20one%20I%20love. %20This%20one%20goes%20out%20to%20the%20one %20I%20left%20behind. c0-param0=Object_Object:{title:reference:c0-e1, author:reference:c0-e2, body:reference:c0-e3} batchId=1 |
图 7 展示了 DWR 发送的数据的格式。它不是那么重要 — 像 DWR 这样的框架的目的之一便是解决格式问题。但了解一下它的工作原理也是很有意思的。
|
很 多 Ajax 工具箱都能简化 Ajax 开发的不同部分。DWR 是一种面向 Java Web 应用程序的端对端工具箱,它简化了应用程序服务器端和客户端的创建。服务器端只需使用一些 Java 注释将服务转变成 Ajax 服务。而在客户端,则使用一个 API 影射服务器上的内容。您仅需要添加一个回调函数。没有比这更简单的了!
|
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
第 3 部分示例代码 | wa-aj-ajaxpro3.zip | 1087KB | HTTP |