DWR 簡化 Ajax 的 portlet 間通信

摘自IBM:http://www.ibm.com/developerworks/cn/java/j-ajaxportlet/index.html


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
    //[email protected]%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 Details portlet,並點擊 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 開發的具體細節,從而簡化了工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章