DWR 简化 Ajax 的 portlet 间通信

Java portlet 和 Ajax 是 Web 开发的绝配

许多开发人员都期待着利用 Ajax 技术来提高基于 Web 的应用程序的用户体验,但是 Ajax 编程可能是一项麻烦的任务。开放源码的 Direct Web Remoting (DWR) 库通过自动把 Java 类转换成 JavaScript 类,可以为 Java™开发人员简化 Ajax 开发。在这篇文章中,将学习如何用 DWR 和符合 JSR-168 规范的 portlet 迅速而容易地构建 Ajax 应用程序。

Sami Salkosuo, 软件架构师, EMC

Sami SalkosuoSami Salkosuo 从 1999 年起一直在 IBM 工作。他的主要兴趣领域是 Java 编程,是 Sun 认证 Java 程序员,IBM 认证的 XML 和相关技术解决方案开发人员,IBM 认证的 IBM WebSphere Portal 解决方案开发人员。除了 Java 技术,他还有 Python、Fortran、LabVIEW、Visual Basic、LISP、Perl 和 PHP 的经验。



2006 年 9 月 04 日

Portlet是基于 Java 平台的 Web 门户应用程序。JSR-168 是开发 portlet 应用程序的 Java Community Process 标准,它描述了 portlet 生命周期管理、portlet 容器合约、打包、部署以及与门户有关的其他方面。

异步 JavaScript + XML(或者叫做 Ajax)是一项用于开发丰富、交互的 Web 应用程序的技术。Ajax 组合了 XML、HTML、DHTML、JavaScript 和 DOM。

Portlet 和 Ajax 看起来彼此之间是完美搭配,因为它们都侧重于用 Web 浏览器作为向用户呈现用户界面的工具。把这两者与 Java 技术组合在一起的简易方式就是使用 DWR 库。DWR 是 Apache 许可下的开放源码 Java 库,用于构建基于 Ajax 的 Web 应用程序。DWR 的基本目的是向开发人员隐藏 Ajax 的细节。您在服务器端使用普通 Java 对象(POJO),而 DWR 动态地生成 JavaScript 代理函数,所以使用 JavaScript 的客户端开发感觉起来就像直接调用 JavaBean。DWR 的主要组件是一个 Java servlet,处理从浏览器到服务器的调用。

本文使用 DWR、基于三个 portlet 来构建一个示例 Ajax 应用程序。我将介绍如何把 DWR 与 porlet 应用程序集成,但是我不想深入 DWR 的幕后工作细节;在这个项目的 Web 站点和 developerWorks 的页面上(请参阅 参考资料获得细节)可以找到关于这个库的更多信息。要构建我描述的应用程序,需要 1.3 或以后版本的 Java 平台和符合 JSR-168 规范的的门户环境。我用来开发和测试这个代码的环境包含 IBM Rational Application Developer V6.0、Apache Jetspeed 2.0 portal 和 Java 5.0。

在开始之前,还应当熟悉 portlet 和 Ajax 开发。如果愿意学习关于这些主题的更多内容,请参阅下面的 参考资料小节。可以从 下载小节下载示例应用程序的完整代码,包括 DWR。

构建示例的 portlet 间通信应用程序

我们的示例应用程序有三个 portlet:Orders、Order Details 和 Customer Details;图 1 显示了示例应用程序:

图 1. 示例应用程序
示例应用程序

Orders portlet 有一个订单列表。当用户点击订单号时,该 portlet 把订单号发送给 Order Details 和 Customer Details portlet,这两者然后显示适当的订单和客户细节。

设置开发环境

在可以开发 portlet 之前,需要设置开发环境和 DWR。我使用 IBM Rational Application Developer V6.0,它对 Java portlet 开发具有内置的支持,但是其他开发环境也可以。按以下步骤开始:

  1. 创建一个新的 JSR-168 portlet 项目。给项目起名为 InterPortletMessaging,但是现在还不创建任何 portlet。
  2. 下载 dwr.jar(版本 1.1;请参阅 参考资料中的链接)。把 dwr.jar 添加到项目的 /WebContent//WEB-INF/lib 目录。
  3. 打开 web.xml 并添加清单 1 中的代码。这会把 DWR servlet 添加到应用程序。这个 servlet 被用在后台处理请求并把响应发送回浏览器。

    清单 1. DWR servlet
    <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>
  4. 用清单 2 中的代码在 WEB-INF 目录创建一个名为 dwr.xml 的文件。这个文件是 DWR 的配置文件,它告诉容器哪些类在 JavaScript 中是可用的。DWR 读取这个 XML 文件并动态地生成把 Java 类表示成 JavaScript 类的 JavaScript 代码。这样您就可以在浏览器中提供由这些 Java 类提供的功能。目前还没有真正创建在清单 2 中引用的 Java 类,但是我们先来看一下。

    清单 2. 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="MessagingBean"> 
          <param name="class" value="msg.MessagingBean"/> 
        </create> 
      </allow> 
     </dwr>

现在可以在 portlet 中使用 DWR 了。但是在创建示例 portlet 之前,需要创建一个消息 bean 和一个伪装数据库 bean(充当示例应用程序的后端)。

创建 MockupDB 和 MessagingBean

清单 3 所示的 MockupDB是个单体类,模拟客户订单的数据库。所有订单都硬编码在这个类中。真实应用程序可能使用关系数据库系统,但这个示例对我们的目的来说足够了。

清单 3. MockupDB
 package db; 

 import java.util.Hashtable; 
 import java.util.Map; 

 public class MockupDB { 

  private static MockupDB instance=new MockupDB(); 

  private String[] orders=new String[4]; 
  private Map orderDetails=new Hashtable(); 
  private Map customerDetails=new Hashtable(); 

  private MockupDB() 
  { 
    String ordStart="ORD"; 
    orders[0]=ordStart+"000408015"; 
    orders[1]=ordStart+"001600023"; 
    orders[2]=ordStart+"000042000"; 
    orders[3]=ordStart+"011235813"; 

    orderDetails.put(orders[0],"1. WebSphere Everyplace Connection Manager<br/>"+ 
                     "2. WebSphere Portal"); 
    orderDetails.put(orders[1],"1. DB2 Universal Database<br/>2. DB2 Everyplace"); 
    orderDetails.put(orders[2],"1. Tivoli Access Manager for e-business <br/>2."+ 
                     "Tivoli Directory Integrator"); 
    orderDetails.put(orders[3],"1. IBM System z9<br/>2. IBM System p5 550 Express"); 

    customerDetails.put(orders[0],"<b>Systems and Technology Group</b><br/>"+ 
                        "Some Road<br/>Finland"); 
    customerDetails.put(orders[1],"<b>Global Financing</b><br/>Another Street"+ 
                        "<br/>Finland"); 
    customerDetails.put(orders[2],"<b>Software</b><br/>Yet Another Road"+ 
                        "<br/>Finland"); 
    customerDetails.put(orders[3],"<b>Global Services</b><br/>Still Another "+ 
                        "Street<br/>Finland"); 
  } 
    
  public static MockupDB getInstance() 
  { 
    return instance; 
  } 
    
  public String[] getOrders() 
  { 
    return orders; 
  } 
    
  public String getOrderDetails(String orderNro) 
  { 
    return (String)orderDetails.get(orderNro); 
  } 

  public String getCustomerDetails(String orderNro) 
  { 
    return (String)customerDetails.get(orderNro); 
  } 
 }

清单 4 所示的 MessagingBean是个简单的 POJO,有两个方法,都接受订单号,但是分别返回订单细节和客户细节。MessagingBeanMockupDB得到细节。

清单 4. MessagingBean
 package msg; 

 import javax.servlet.http.HttpSession; 

 import db.MockupDB; 

 public class MessagingBean { 

  public MessagingBean() 
  {        
  } 
    
  public String getOrderDetails(String orderNumber,HttpSession httpSession) 
  { 
    String orderDetails=MockupDB.getInstance().getOrderDetails(orderNumber) 
    httpSession.setAttribute("orderDetailsOrderNumber",orderNumber); 
    httpSession.setAttribute("orderDetails",orderDetails); 
    return orderDetails; 
  } 

  public String getCustomerDetails(String orderNumber,HttpSession httpSession) 
  { 
    String customerDetails=MockupDB.getInstance().getCustomerDetails(orderNumber); 
    httpSession.setAttribute("customerDetailsOrderNumber",orderNumber); 
    httpSession.setAttribute("customerDetails",customerDetails); 
    return customerDetails; 
  } 
 }

MessagingBean还把订单细节和客户细节添加到 HttpSession

javaScriptFunctions.jsp

javaScriptFunctions.jsp 导入了来自 DWR 的 JavaScript 库(engine.js)并动态地创建库 MessagingBean.js。注意,MessagingBean.js 使用的名称与 dwr.xml(清单 2)中的 JavaBean 的名称相同;实际上,DWR 生成 MessagingBean.js。DWR 框架使用 engine.js 库;作为开发人员,通常不需要考虑直接使用它。

如清单 5 所示,sendOrderNr()函数调用 清单 4中定义的 MessagingBean函数。DWR 自动把 HttpSession添加到方法调用。JavaScript 函数中的最后一个参数是 callback函数。在稍后创建的 portlet JSP 中,包含这个 JSP。

清单 5. javaScriptFunctions.jsp
 <%@ page contentType="text/html" 
 import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %> 
 <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %> 
 <portlet:defineObjects/> 

 <SCRIPT type="text/javascript"
	 src='<%= renderResponse.encodeURL(renderRequest.getContextPath() + 
	"/dwr/interface/MessagingBean.js") %>'> 
 </SCRIPT> 

 <SCRIPT type="text/javascript"
	 src='<%= renderResponse.encodeURL(renderRequest.getContextPath() + 
	"/dwr/engine.js") %>'> 
 </SCRIPT> 

 <SCRIPT type="text/javascript"> 

 function <portlet:namespace />sendOrderNr(orderNr) 
 { 
 document.getElementById("orderDetailsOrderNumber").innerHTML=orderNr; 
 document.getElementById("customerDetailsOrderNumber").innerHTML=orderNr; 
 MessagingBean.getOrderDetails(orderNr,<portlet:namespace />showOrderDetails); 
 MessagingBean.getCustomerDetails(orderNr,<portlet:namespace />showCustomerDetails); 

 return false; 
 } 

 function <portlet:namespace />showOrderDetails(orderDetails) 
 { 
 document.getElementById("orderDetails").innerHTML=orderDetails; 
 return false; 
 } 

 function <portlet:namespace />showCustomerDetails(customerDetails) 
 { 
 document.getElementById("customerDetails").innerHTML=customerDetails; 
 return false; 
 } 
 </SCRIPT>

创建 portlet

现在有了后端和代理函数,可以开发 portlet 本身了。所有三个 portlet 都使用相同的代码基;惟一的区别是每个 portlet 使用的 JSP 的名称。

  1. 使用清单 6 中的代码创建一个新 portlet,并给它起名为 Orders:

    清单 6. Orders.java
     package interportletmessagingusingajax; 
    
     import java.io.*; 
    
     import javax.portlet.*; 
    
     public class Orders extends GenericPortlet { 
    
      // JSP folder name 
      public static final String JSP_FOLDER = "/interportletmessagingusingajax/jsp/"; 
    
      // JSP file name to be rendered on the view mode 
      public static final String VIEW_JSP = "OrdersView";         
    
    
      public void init(PortletConfig config) throws PortletException{ 
        super.init(config); 
      } 
    
      public void doView(RenderRequest request, RenderResponse response) 
        throws PortletException, IOException { 
        // Set the MIME type for the render response 
        response.setContentType(request.getResponseContentType()); 
    
        // Invoke the JSP to render 
        PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher( 
          getJspFilePath(request, VIEW_JSP)); 
        rd.include(request,response); 
    
        //this is workaround for portletsession sharing between 
        //servlets and portlets 
        //see http://weblogs.java.net/blog/wholder/archive/2005/02/session_session.html 
        //and http://mail-archives.apache.org/mod_mbox/portals-pluto-dev/200502.mbox/%3Ca 
        //2519328f3ba1d1eddfc33c924b6805d@umich.edu%3E 
        // 
        PortletRequestDispatcher rd2 = getPortletContext().getRequestDispatcher("/dwr/"); 
        rd2.include(request, response); 
    
      } 
    
      private static String getJspFilePath(RenderRequest request, String jspFile) { 
        String markup = request.getProperty("wps.markup"); 
        if( markup == null ) 
          markup = getMarkup(request.getResponseContentType()); 
        return JSP_FOLDER+markup+"/"+jspFile+"."+getJspExtension(markup); 
      } 
    	
      private static String getMarkup(String contentType) { 
        if( "text/vnd.wap.wml".equals(contentType) ) 
          return "wml"; 
        return "html"; 
      } 
    
      private static String getJspExtension(String markupName) { 
        return "jsp"; 
      } 
     }
  2. 创建并打开 OrdersView.jsp(在 interportletmessagingusingajax/jsp/html 目录),并把清单 7 中的代码添加到它:

    清单 7. OrdersView.jsp
     <%@ page contentType="text/html" 
     import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %> 
     <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %> 
     <portlet:defineObjects/> 
     <jsp:include page="javascriptFunctions.jsp" /> 
    
     <DIV style="margin: 6px"> 
    
     <H4 style="margin-bottom: 3px">Orders</H4> 
     <table cellspacing="0" cellpadding="5" border="1"> 
     <% db.MockupDB database= db.MockupDB.getInstance(); 
    
     String[] orders=database.getOrders(); 
     for(int i=0;i<orders.length;i++) 
     { 
     %> 
     <tr> 
    
     <td><%="000000000"+String.valueOf(i+1) %></td> 
     <td><a href="" onclick="return <portlet:namespace />sendOrderNr('<%= 
      orders[i]%>');"><%=orders[i]%></a></td> 
     </tr> 
     <% 
     } 
     %> 
    
     </table> 
     </DIV>
  3. 第二个 portlet 是 OrderDetailsPortlet.java。对这个 portlet 使用 清单 6中的代码,并把 VIEW_JSP变量的值改成 OrdersDetailsPortletView.jsp。这个 JSP 的代码如清单 8 所示:
    清单 8. OrdersDetailsPortletView.jsp
    <%@ page contentType="text/html" 
    import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %>
    <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %>
    <portlet:defineObjects/> 
    
    <DIV style="margin: 6px"> 
    
    <H4 style="margin-bottom: 3px">Order details</H4>
    
    <table cellspacing="0" cellpadding="5" border="1">
    <tr> 
    <th>Order number</th> 
    <th>Order details</th> 
    </tr> 
    
    <tr> 
    <% 
    String orderDetailsOrderNumber=(String)renderRequest.getPortletSession().getAttribute(
     "orderDetailsOrderNumber",PortletSession.APPLICATION_SCOPE); 
    String orderDetails=(String)renderRequest.getPortletSession().getAttribute( 
     "orderDetails",PortletSession.APPLICATION_SCOPE); 
    
    if(orderDetailsOrderNumber==null) 
    { 
    orderDetailsOrderNumber=""; 
    } 
    
    if(orderDetails==null) 
    { 
    orderDetails=""; 
    } 
    %> 
    <td><div id="orderDetailsOrderNumber"><%=orderDetailsOrderNumber%>
    </div></td> 
    <td><div id="orderDetails"><%=orderDetails%></div></td>
    </tr> 
    
    
    </table> 
    </DIV>
  4. 第三个 portlet 是 CustomerDetailsPortlet.java。对这个 portlet 使用 清单 6中的代码,并把 VIEW_JSP变量的值改成 CustomerDetailsPortletView.jsp。这个 JSP 的代码如清单 9 所示:
    清单 9. CustomerDetailsPortletView.jsp
     <%@ page contentType="text/html" 
     import="java.util.*,javax.portlet.*,interportletmessagingusingajax.*" %> 
     <%@taglib uri="http://java.sun.com/portlet" prefix="portlet" %> 
     <portlet:defineObjects/> 
    
     <% 
     %> 
    
     <DIV style="margin: 6px"> 
    
     <H4 style="margin-bottom: 3px">Customer details</H4> 
     <table cellspacing="0" cellpadding="5" border="1"> 
     <tr> 
     <th>Order number</th> 
     <th>Customer details</th> 
     </tr> 
    
     <tr> 
     <% 
     String customerDetailsOrderNumber= 
      (String)renderRequest.getPortletSession().getAttribute( 
      "customerDetailsOrderNumber",PortletSession.APPLICATION_SCOPE); 
     String customerDetails=(String)renderRequest.getPortletSession().getAttribute( 
      "customerDetails", PortletSession.APPLICATION_SCOPE); 
    
     if(customerDetailsOrderNumber==null) 
     { 
     customerDetailsOrderNumber=""; 
     } 
    
     if(customerDetails==null) 
     { 
     customerDetails=""; 
     } 
     %> 
     <td><div id="customerDetailsOrderNumber"><%=customerDetailsOrderNumber%> 
     </div></td> 
     <td><div id="customerDetails"><%=customerDetails%></div></td> 
     </tr> 
    
     </table> 
     </DIV>

示例应用程序现在准备好了。下一步是把 portlet 打包成 WAR 文件并在 Apache Jetspeed 门户中测试它。


测试示例应用程序

在这一节,将看到示例应用程序的作用。首先,创建 portlet WAR 并把它安装到 Jetspeed 门户。然后,把三个 portlet 添加到门户,看它们是如何工作的。将把它们全都构建到一个页面,但如果需要也可以把它们放到多个页面;幕后的机制仍然起作用。

把 portlet 应用程序安装到 Jetspeed

把 portlet WAR 文件安装到 Jetspeed 的方法是把 WAR 文件拷贝到 <Jetspeed install dir>/webapps/jetspeed/WEB-INF/deploy 目录。然后 Jetspeed 会自动安装 portlet,portlet 即可使用了。

使用以下步骤把新页面添加到 Jetspeed 门户:

  1. 进入 Jetspeed 门户,并作为管理员登录。
  2. 点击右下角的 Edit图标,并添加名为 Inter-Portlet Messaging的新页面,如图 2 所示:

    图 2. 添加新页面
    添加新页面
  3. 选择 Inter-Portlet Messaging页面并点击 Edit图标。然后点击 Add a portlet图标,在这个页面上添加 portlet。选择 Orders、Order DetailsCustomer Detailsportlet,并点击 Select portlets,把选中的 portlet 添加到门户页面。完成之后,页面看起来应当像图 3:
    图 3. 页面上的 Portlet
    页面上的 Portlet

Portlet

Orders portlet 如图 4 所示,列出订单:

图 4. Orders portlet
Orders portlet

在点击订单号时,其他 portlet 显示这个订单的细节。Customer Details portlet 显示客户信息,如图 5 所示。信息检索自 MockupDB

图 5. Customer Details portlet
Customer Details portlet

Order Details portlet 也显示检索自 MockupDB的信息,如图 6 所示:

图 6. Order Details portlet
Order Details portlet

如果喜欢,可以回过去,向不同的页面添加一个或多个 portlet。将会看到,portlet 不需要在单个页面上,因为 portlet 内容保存在用户会话中。


结束语

这篇文章介绍了用 Ajax 实现 portlet 间通信的一种方式。Ajax 是开发交互式 Web 页面的一种非常强大的技术,而支持 Ajax 的 portlet 通过消除门户中典型存在的请求 - 响应延迟,极大地改善了用户体验。

可以用本文中的代码作为开发您自己的应用程序的起点;文中的代码还显示了 DWR 如何把 Java 编程模型扩展到 Web 浏览器。使用 DWR,JavaBean 几乎就像是在浏览器中可用一样。DWR 几乎隐藏了 Ajax 的全部细节,让您可以专注于手头的工作,而不必考虑 Ajax 开发的具体细节,从而简化了工作。


下载

描述名字大小
Source codej-ajaxportlet.war3MB

参考资料

学习

获得产品和技术

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, WebSphere, Web development
ArticleID=157539
ArticleTitle=DWR 简化 Ajax 的 portlet 间通信
publish-date=09042006