内容


像专业人员一样开发 Ajax 应用程序,第 3 部分

使用 DWR、Java 和 Dojo 工具箱集成 Java 和 JavaScript

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 像专业人员一样开发 Ajax 应用程序,第 3 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:像专业人员一样开发 Ajax 应用程序,第 3 部分

敬请期待该系列的后续内容。

本文是包含 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 调试工具。在 参考资料 部分可以获得这些工具的相关链接。

Direct Web Remoting (DWR) 简介

Ajax 应用程序看上去有些神秘,幸好开发应用程序的过程还比较直观。对于每个 Ajax 交互,必须在服务器上创建一个端点(从从事 Web 服务的朋友那里借用的术语),并且还要创建客户端代码来调用该端点。此外,还必须创建用于序列化客户机和服务器之间的数据流的所有代码。这些服务器端点可以是泛型服务,甚至可以是 REST 式的端点。但是,它们的创建通常特定于客户机的需要。有时应该避免紧密耦合,但有时又应该采用。对于后一种情况,DWR 是一种一揽子的解决方案。它允许您将服务器端代码作为 Ajax 端点公开,而所有模板都是自动生成的。现在,让我们通过一个特定的示例来看看 DWR 是如何工作的。

示例应用程序:Ajax 留言板

这里使用的示例应用程序是一个简单的留言板。数据模型已经大大简化,以便您能把精力放在借助 DWR 的 Ajax 交互。我们先来研究一下应用程序的后端,看看 DWR 如何能层叠于其上以启用应用程序的 Ajax 功能。

设置后端

先为留言板创建一个数据库表。清单 1 给出了创建此表所需的 SQL 脚本。

清单 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 所示。

清单 2. 留言 Java 类
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,它给出了此应用程序的服务代码。

清单 3. MessageService
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 servlet

DWR 使用 Java servlet 接受和响应 Ajax 请求。这个 servlet 包括在 DWR 库内,因此只需配置 Web 应用程序,以便在应用程序的 web.xml 文件内启用 servlet。清单 4 给出了示例应用程序的 web.xml。

清单 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-paramsclasses 参数是一个以逗号分隔的列表,它由带有 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 所示。

图 1. DWR 调试屏幕
DWR 调试屏幕
DWR 调试屏幕

该图显示出了用 @RemoteProxy 声明标注的所有 Java 类。单击任何一个类,就会显示相关细节,如图 2 所示。

图 2. MessageService JavaScript 信息
MessageService JavaScript 信息
MessageService JavaScript 信息

这个界面展示了如何在 Web 页面上引用动态 JavaScript。这里有一个必须包含的核心库(engine.js),以及一个特定于此应用程序的纯动态库(MessageService.js)。这里的一个可选实用工具库提供了许多 helper 函数。

掌握了如何引用 DWR JavaScript 后,还需知道如何使用它。可以使用 MessageBoard 页面测试它,如图 3 所示。

图 3. 测试 DWR Ajax
测试 DWR Ajax
测试 DWR Ajax

此图展示了 getMessages() 调用的 Ajax 请求的结果。所显示的数据是一个 JavaScript 对象(JSON)的数组。我们知道了 API 将要返回的内容,但如何调用它呢?我们看看测试页面的源代码,如清单 5 所示。

清单 5. MessageBoard 测试页面的源代码
<li>
  getMessages(  );
  <input class='ibutton' type='button' onclick='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' onclick='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 所示。

清单 6. MessageBoard Web 页面
 <!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。

图 4. MessageBoard 页面
MessageBoard 页面
MessageBoard 页面

该页使用支持 DWR 的 Ajax 异步加载数据。因此能够显示消息,但如何保存新消息呢?这可通过页面底部的表单来实现。单击 Add Message 时,使用 DWR 的实用方法 dwr.util.getValue 从表单获得数据。此实用工具函数适用于任何 HTML 元素,包括 div、文本输入、复选框和选择列表。获得数据后,将其放入 JavaScript 对象并调用 MessageService.saveMessage()。在本例中,由于响应为空,所以没有指定处理函数,而是重用了前面的 addRows 函数,用来向表追加新行。这使 UI 的响应性变得很好。

填写表单并单击 Add Message,可以测试该页。使用类似 Firebug 的工具可以查看 HTTP 流量。清单 7 给出了 saveMessage 调用发送的数据。

清单 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 影射服务器上的内容。您仅需要添加一个回调函数。没有比这更简单的了!


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=333661
ArticleTitle=像专业人员一样开发 Ajax 应用程序,第 3 部分: 使用 DWR、Java 和 Dojo 工具箱集成 Java 和 JavaScript
publish-date=08292008