-
摘要
JSR-168是適合於portlet開發人員的Java API集合。設計符合規范的JSR-168 portlet的原因有很多。可移植性就一個顯而易見的好處。根據規范編寫的代碼更容易在門戶服務器之間移動。多數基於Java的門戶服務器都支持JSR-168 portlet。
另一個好處是更易於聯合。當portlet符合JSR-168規范時,通過Web Services for Remote Portlets (WSRP)生產者公開JSR-168 Portlet會更容易一些。WSRP提供了一個通過Web service聯合portlet內容的標准。JSR-168和WSRP 1.0 portlet功能是緊密耦合的。JSR-168 to WSRP portlet橋利用JSR-168的URL重寫API。本文將闡述開發JSR-168 portlet以便獲得可移植性的最佳實踐。
1. 總是利用URL重寫API,以獲得Portlet中的內容
Java開發人員經常在如下所示JSP中編寫圖像的URL:
<img src="/<%= request.getContextPath()%>/images/logo.gif"/>
這在JSR-168 portlet中是不正確的。正確的方法是:
<img src="<%= renderResponse.encodeURL(renderRequest.getContextPath()+
"/images/logo.gif") %>"/>
encodeURL()方法可以采用完全路徑URI或者完全限定URL。完全路徑URI是最常用的。在使用JSR-168 portlet將資源嵌入Web Application Archive (WAR)中時,可以使用此技術。在將圖像放置到單獨服務器上時,可以使用完全限定URL。專門為靜態內容提供服務的緩存服務器就是一個示例,它卸掉來自門戶服務器的通信量。盡管可以通過對完全限定URL使用encodeURL()來引用portlet以外的內容,但應該只在無法通過客戶機訪問資源時這樣做。如果客戶機可以直接浏覽資源,則無需對URL使用encodeURL()。例如,如果有一台Web服務器,可用該服務器獲得門戶用戶無法直接浏覽的防火牆內的靜態內容,則需要調用encodeURL()。如果這些內容在防火牆之外,並且門戶用戶可以直接浏覽到Web服務器,則無需調用encodeURL()。
2. 不要將路徑附加到重寫URL中
傳入RenderRequest的encodeUrl()方法中的URL在調用該方法之前必須是完整的。在調用該方法之後,無法添加URL的某些部分。例如,如果想從XSLT轉換中生成一個URL轉換,則不能將已編碼的基本URL(http://foo.com/)作為參數傳遞,並將路徑(pages/bar.JSP)附加到該轉換中的已編碼基本URL中。
以下調用演示了將URL編碼到圖像中的正確方式:
<@= renderResponse.encodeURL(renderRequest.getContextPath()+
"/images/logo.gif")@>
它使用一個.portal文件在BEA WebLogic Portal 9.2中生成以下Html片段:
<img src="http://localhost:7001/PortalWebApp/images/logo.gif;
PORTAL_TAU=W3f6FbmLLcgZq9Fpv1JHLs5rrJG8Lgj2nnDVJqdfShhRGFnsqCKZ!-545815275"/>
以下調用是不正確的。URL並不指向想要的資源。
<@= renderResponse.encodeURL(renderRequest.getContextPath()+
"/images/")+"logo.gif"@>
它使用.portal文件在WebLogic Portal 9.2中生成以下Html文件:
<img src="http://localhost:7001/PortalWebApp/images/;PORTAL_TAU=W3f6FbmLLcgZq9Fpv1JHLs5rrJG8Lgj2nnDVJqdfShhRGFnsqCKZ!-545815275logo.gif"/>
3. 使用名稱空間限定客戶端腳本變量和方法
假設您想使用portlet中的Javascript驗證用戶輸入。以下JavaScript功能可能很有用:
<script>
function validate(foo) {
if (foo.bar.value=="") {
return false;
}
return true;
}
</script>
同一頁面中的其他portlet可能也有一個命名為validate()的具有不同邏輯的Javascript方法。門戶框架本身可能使用JavaScript方法。這個問題的解決方法是使用客戶端腳本中的名稱空間方法和頂層變量。<portlet:namespace/>標記將為每個portlet生成一個惟一標識符。第一步是通過taglib directive將標記庫包含在JSP中。
<%@taglib uri="http://Java.sun.com/portlet" prefix="portlet"%>
腳本中的validate()方法可以對標記加以區分。
<script>
function validate<portlet:namespace/>(foo) {
if (foo.bar.value=="") {
return false;
}
return true;
}
</script>
以下是調用帶名稱空間的JavaScript方法的方式:
<form action="http://www.somesite.org/servlet"
method="GET" onsubmit="return validate<portlet:namespace/>(this);">
<label for="bar">Text(required): </label>
<input type="text" name="bar" id="bar">
</form>
4. 確保引用Portlet資源的內聯客戶端腳本符合規范
客戶端腳本常常引用外部資源(如圖像、電影和外部頁面)來增強用戶界面。常見的示例是預先加載圖像以使交換圖像更有效的JavaScript。以下是一個示例:
<script>
function preloadImages(){
var menuImage =
new Image();
menuImage.src = "images/icon.gif";
var menuImageDark=new Image();
menuImageDark.src = "images/icon.gif";
}
</script>
客戶端腳本中的URL必須根據JSR-168規范進行重寫。這些腳本必須在JSP或JSP-168 portlet類中,以便調用重寫API的URL。它們不能在單獨的JavaScript (.js)文件中。以下是一個包含URL重寫的適當名稱空間腳本在JSR-168 portlet中看起來的樣子:
<script>
function <portlet:namespace/>preloadImages(){
var menuImage = new Image();
menuImage.src = "<%=renderResponse.encodeURL(renderRequest.getContextPath()+ "images/icon.gif")%>";
var menuImageDark= new Image();
menuImageDark.src = "<%=renderResponse.encodeURL(renderRequest.getContextPath()+ "images/icon_dark.gif") %>";
}
</script>
5. 總是為portlet響應聲明一個內容類型
根據JSR-168規范,“portlet必須使用RenderResponse接口的setContentType方法設置響應的內容類型”。沒有顯式設置其內容類型的portlet仍然會成功獲得編譯。但WebLogic Portal不會執行沒有設置其內容類型的portlet。確保您的portlet設置了其內容類型。
以下示例演示了一個正確設置其內容類型的portlet:
public class MyPortlet extends GenericPortlet {
public void doVIEw(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
response.setContentType("text/Html");
PrintWriter writer = response.getWriter();
writer.println("I set my content type!");
}
}
此示例是不正確的,但仍將獲得編譯:
public class MyPortlet extends GenericPortlet {
public void doVIEw(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
// no content type set!
PrintWriter writer = response.getWriter();
writer.println("I did NOT set my content type!");
}
}
6. 不要從Portlet發送CookIE
根據JSR-168 portlet規范,在HttpServletResponse上調用addCookie()實際上不會設置一個cookie。允許設置cookIE的portlet容器被打破。不要調用此方法。
如果您喜歡在用戶使用門戶的時候基於每位用戶持久存儲信息,那麼可以將信息存儲為portlet會話中的一個屬性。如果您喜歡在用戶退出後持久存儲信息,那麼可以將信息存儲到數據存儲庫(文件系統、數據庫、LDAP等)中。
7. 將業務邏輯從表示中分離出來
有經驗的開發人員都知道模型查看器控制器框架類似於Struts或Beehive,可以使開發富Web應用程序變得更容易。這同樣也適用於portlet。JSR-168並不是適用於平台獨立portlet的惟一理想規范。WSRP portlet在實現標准的門戶(包括非Java門戶)之間移動很方便。WebLogic Portal 可以通過WSRP公開Beehive和Struts portlet。
如果需要將portlet部署為JSR-168 WAR,您仍然有一些選擇。將業務邏輯從JSR-168 portlet的表示邏輯中分離出來的最簡單方法是指派一個JavaServer Page (JSP)。portlet處理呈現方法(比如render()和doVIEw())中的業務邏輯。portlet使用應用程序級作用域或portlet作用域將信息傳遞給JSP。下面的示例將一個portlet請求指派給JSP,並傳遞portlet作用域中的一個字符串:
public void doVIEw(RenderRequest request, RenderResponse response) throws PortletException, IOException {
response.setContentType("text/Html");
request.setAttribute("foo","bar");
String JSp = "/pages/portal.JSP";
PortletContext ctx = getPortletContext();
PortletRequestDispatcher dispatcher = ctx.getRequestDispatcher(JSP);
dispatcher.include(request, response);
}
到達JSP(上述示例中的JSP)的路徑值並不包括portlet的Web歸檔文件(WAR)的上下文路徑。
JSR-168的指派方法允許將業務邏輯與表示分離。不過,它們缺乏MVC框架的成熟度。
適用於JSR-168開發的框架包括:
Spring Portlet MVC
WebWork
Struts Action 2
Struts Action 2是Struts和WebWork的組合,因此portlet代碼庫對現在而言幾乎是一樣的。這些框架簡化了復雜portlet的開發和維護。
結束語
遵守這些指導原則會使您的portlet符合JSR-168規范。遵守規范會使您的portlet在Java門戶服務器之間移動變得更容易。還會使利用WSRP聯合門戶內容變得更容易。
參考資料
Java Community Process JSR-168 主頁
OASIS WSRP 主頁
WebLogic Portal 8.1 中的 URL(中文版,Dev2Dev,2005年5月)
利用WebLogic Portal 8.1 SP3開發Java Portlets(中文版,Dev2Dev,2004年8月)