服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > 专题栏目 > WEB2.0新技术 > 查看文档

dwr 简化 ajax 的 portlet 间通信

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

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

  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 间通信应用程序

  我们的示例应用程序有三个 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。把 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

  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,有两个方法,都接受订单号,但是分别返回订单细节和客户细节。messagingbean 从 mockupdb 得到细节。

  清单 4. messagingbean

package db;

import java.util.hashtable;
import java.util.map;

public class mockupdb {

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 文件拷贝到 /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 details 和 customer details portlet,并点击 select portlets,把选中的 portlet 添加到门户页面。完成之后,页面看起来应当像图 3:

点击放大此图片

  图 3. 页面上的 portlet

  portlet

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

点击放大此图片

  图 4. orders portlet

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

点击放大此图片

  图 5. customer details portlet

  order details portlet 也显示检索自 mockupdb 的信息,如图 6 所示:

点击放大此图片

  图 6. order details portlet

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

  结束语

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

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

下载本文示例代码     查看原文

扫描关注微信公众号