摘自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: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 開發具有內置的支持,但是其他開發環境也可以。按以下步驟開始:
- 創建一個新的 JSR-168 portlet 項目。給項目起名爲 InterPortletMessaging,但是現在還不創建任何 portlet。
- 下載 dwr.jar(版本 1.1;請參閱 參考資料 中的鏈接)。把 dwr.jar 添加到項目的 /WebContent//WEB-INF/lib 目錄。
- 打開 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> - 用清單 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(充當示例應用程序的後端)。
清單 3 所示的 MockupDB
是個單體類,模擬客戶訂單的數據庫。所有訂單都硬編碼在這個類中。真實應用程序可能使用關係數據庫系統,但這個示例對我們的目的來說足夠了。
清單 3. MockupDB
package db; |
清單 4 所示的 MessagingBean
是個簡單的 POJO,有兩個方法,都接受訂單號,但是分別返回訂單細節和客戶細節。MessagingBean
從 MockupDB
得到細節。
清單 4. MessagingBean
package msg; |
MessagingBean
還把訂單細節和客戶細節添加到 HttpSession
。
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" |
現在有了後端和代理函數,可以開發 portlet 本身了。所有三個 portlet 都使用相同的代碼基;惟一的區別是每個 portlet 使用的 JSP 的名稱。
- 使用清單 6 中的代碼創建一個新 portlet,並給它起名爲 Orders:
清單 6. Orders.javapackage 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";
}
} - 創建並打開 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> - 第二個 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> - 第三個 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 WAR 文件安裝到 Jetspeed 的方法是把 WAR 文件拷貝到 <Jetspeed install dir>/webapps/jetspeed/WEB-INF/deploy 目錄。然後 Jetspeed 會自動安裝 portlet,portlet 即可使用了。
使用以下步驟把新頁面添加到 Jetspeed 門戶:
- 進入 Jetspeed 門戶,並作爲管理員登錄。
- 點擊右下角的 Edit 圖標,並添加名爲 Inter-Portlet Messaging 的新頁面,如圖 2 所示:
圖 2. 添加新頁面 - 選擇 Inter-Portlet Messaging 頁面並點擊 Edit 圖標。然後點擊 Add a portlet 圖標,在這個頁面上添加 portlet。選擇 Orders、Order Details 和 Customer Details portlet,並點擊 Select portlets,把選中的 portlet 添加到門戶頁面。完成之後,頁面看起來應當像圖 3:
圖 3. 頁面上的 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 開發的具體細節,從而簡化了工作。