想要用好Struts應用框架,必須了解J2EE Web級JSP和Servlet技術存放共享對象的幾種方式。同時,要利用J2EE 開發Web應用程序也必須掌握組件間對象共享的機制。
像Java程序有類級別變量、方法級別變量一樣,J2EE Web應用程序有四個對象存放共享對象。這些共享對象存放在那里,以便存放者或者其它程序代碼日后使用。這四個對象分別是頁面、請求、會話和應用程序,它們都是以數據結構鍵/值對的形式保存的。同時這四個對象形成了四個級別的共享對象存放地,即應用程序對象中的共享對象是全局性的,在整個應用程序的生命周期內有效(當然主動去掉除外),屬于所有的上網用戶;會話對象中的共享對象是在一個會話期內有效,屬于用戶的當前會話;請求對象中的共享對象在一個請求期內有效,屬于用戶發送的當前請求;頁面對象中的共享對象只屬于當前頁面的執行實例。本文主要分析共享對象的設置和訪問方法,包括共享對象的有效范圍、具體訪問方法、輔助顯示手段和多線程下的實現策略。
在JSP中訪問共享對象
Servlet運行時已經準備好了這些范圍對象,如表1所示。
表1 JSP中的共享對象
|
變量名 |
變量類名 |
對象可訪問范圍 |
頁面 |
pageContext |
javax.servlet.jsp.PageContext |
在執行某一個JSP時,Servlet運行時會為它初始化pageContext變量,這個變量可以被整個JSP代碼訪問,包括INCLUDE指示符插進來的代碼。 |
請求 |
ruquest |
javax.servlet.http.HttpServletRequest |
用戶提交一個HTTP請求給Servlet容量,Servlet運行時會把請求封裝成HttpServletRequest的一個實例,在JSP中表現為request變量。能訪問pageContext的JSP代碼也能訪問request,另外被處理這個請求的JSP代碼FORWARD到的JSP代碼也能訪問。 |
會話 |
session |
javax.servlet.http.HttpSession |
一個HttpSession會話由被創建到關閉或失效期間的用戶請求組成。處理這些請求的JSP可以訪問到這期間的session對象中的共享對象。在會話關閉或失效時,這些對象會丟失。 |
應用程序 |
application |
javax.servlet.ServletContext |
這個對象在應用程序的整個生命周期間都有效,存放在這個對象內的數據任何JSP都能訪問到。 |
在Servlet中訪問共享對象
Servlet中的共享對象如表2。
表2 Servlet中的共享對象
請求 |
SERVLET類的一系列服務方法的request參數。 |
javax.servlet.http.HttpServletRequest |
用戶提交一個HTTP請求給Servlet容器,Servlet運行時會把請求封裝成HttpServletRequest的一個實例,并作為Servlet服務方法的request參數傳遞給Servlet。這個Servlet也可以把這個實例傳遞給其它Web組件。 |
會話 |
request.getSession()或者request.getSession(boolesn)方法獲得。 |
javax.servlet.http.HttpSession |
一個HttpSession會話由被創建到關閉或失敗期間的用戶請求組成。處理這些請求的Servlet可以訪問到這期間的session對象中的共享對象。在會話關閉或失效時,這些共享對象會丟失。 |
應用程序 |
SERVLET的.getServletContext() |
javax.servlet.ServletContext |
這個對象在應用程序的整個生命周期間都有效,存放在這個對象內的數據任何Web組件都能訪問到。 |
資源組合
在JSP技術中,有兩種把資源片斷組合成一個資源的技術:include 指示符和jsp:include元素。指示符的語法為:
<%@ include file="fragmentresource.jsp" %>
|
當一個JSP被翻譯成Servlet時,它會被處理。jsp:include元素的語法是:
<jsp:include page="included.jsp"/>
|
當這個JSP頁面被執行時,它會被處理。指示符是代碼的組合,元素則是結果的組合。fragmentresource.jsp和主頁面具有一樣的上下文,如頁面對象;而included.jsp不具有和主頁面一樣的頁面對象,但請求對象是同一個。
在Servlet中,RequestDispatcher.include(request,response)實現結果的整合,示例代碼如下:
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
dispatcher.include(request,response);
|
控制傳遞
在利用RequestDispatcher.forware(request,response)把控制傳給另一個Web組件設置形成一個控制管道時,要嚴格遵循“前面的組件處理request,最后的組件處理response”的準則。前面的組件甚至不能企圖獲取response輸出流的引用。這個控制管道中的組件具有同一個request對象,不具有相同的pageContext對象(針對JSP)。Servlet中傳遞控制的例子如下:
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
dispatcher.forward(request,response);
|
JSP中傳遞控制的例子如下:
<jsp:forward page="/main.jsp"/>
|
在JSP中,可以通過jsp:param元素來增加請求對象的參數,適用于jsp:include和jsp:forward兩元素。 示例如下:
<jsp: forward page="included.jsp">
<jsp:param name="param1 " value=="value1"/>
</jsp:include>
|
顯示共享對象

圖1 顯示共享信息類圖
在網頁上顯示各個級別的共享對象是一個非常不錯的調試手段。下面的顯示共享對象類圖(如圖1)實現了這個功能。它以類org.i18.struts.AttributeUtils為核心,這個類負責把四個對象的共享對象保存為鍵/值對的HashMap對象。通過它可以得到請求對象中的共享對象及參數信息,以及頁面對象、會話對象、應用對象這些共享對象的函數接口。在JSP頁面中,通過下面的代碼可以給這個類的對象提供輸入:
<jsp:useBean id="bean3" scope="application"
class="org.i18.struts.AttributeUtils" />
<jsp:setProperty name="bean0" property="object" value="<%= pageContext %>" />
|
JSP頁面可以利用Struts提供的logic標簽庫顯示這些共享對象:
<li> 這是頁對象內的共享對象 </li>
<table align="center" cellpadding="5" border="0">
<tbody valign="center">
<tr>
<td class="header"> 共享對象名 </td>
<td class="header"> 共享對象相關內容 </td>
</tr>
<logic:iterate id="element" name="bean0" property="pageProp" >
<tr><td>
<bean:write name="element" property="key"/>
</td>
<td>
<bean:write name="element" property="value"/>
</tr>
</logic:iterate>
</tbody>
</table>
|
在Servlet中,AttributeDisplayHelper幫助者類完成JSP中logic標簽庫相應的功能。幫助者類完成工作后,AttrServlet把幫助者類完成的結果放在request中的一個共享屬性Attr中,接著把控制傳給servletAttr.jsp,再由它訪問AttrServlet在request中設置的共享屬性Attr,并顯示結果。
使用者可以通過attr.jsp、AttrServlet的url映射和index.jsp的提交按鈕來查看當前上下文所有級別的共享對象。
關于多線程問題
J2EE系列規范中,EJB規范保證了組件開發者在單線程的環境下編程,但Servlet規范沒有規定Servlet的系列服務方法在單線程模式下運作,所以開發者在使用共享對象時要注意線程同步問題。一個比較通用的原則是:在一個專職的組件中設置共享對象,當設置和訪問破壞數據的一致性時,使用Java的同步控制。
一個Servlet(包括JSP)的生命周期由其所在的容器控制。當有一個Servlet請求時,容器執行如下步驟:
1.如果此Servlet的實例不存在,容器先裝載Servlet的類代碼,創建一個Servlet實例,接著調用這個實例的init方法。
2.如果此Servlet的實例存在,容器分配一個處理用戶請求的工作線程。如果Servler實現了SingleThreadModel接口,工作線程會在這個實例上同步,并且在取得訪問權限后調用實例的service方法,否則直接調用實例的service方法。javax.servlet.http.HttpServlet的service方法會根據用戶的HTTP請求類型調用相應的doxxx方法。
3.只有當所有的線程從這個實例中退出,容器在回收這個實例時才會調用這個實例的destroy方法。
Servlet實例線程圖(如圖2)體現了這個生命周期模型。

圖2 Servlet實例線程圖
雖然可以讓所有的Servlet實現SingleThreadModel接口,但這會嚴重影響程序的性能。要解決多線程的同步問題,我們首先要分析共享對象的訪問模式。在一個Web程序中,共享對象按訪問模式可以分為以下兩類:一次設置、多次讀取的共享對象和多次設置、多次讀取的共享對象。
一次設置、多次讀取
對于這種共享對象,可以開發一個事件監聽器,監聽程序啟動和停止事件,代碼如下:
package org.i18.listen
import org.i18.utils.*;
import javax.servlet.*;
import util.Counter;
public final class ContextListener implements ServletContextListener {
private ServletContext context =null;
public void contextInitialized(ServletContextEvent event){
context =event.getServletContext();
SynObject synObject = new SynObject();
//在這里把共享對象放在應用對象中
context.setAttribute("SYNOBJECT",synObject);
}
public void contextDestroyed(ServletContextEvent event){
context =event.getServletContext();
//清除保存在應用對象中的共享屬性
context.removeAttribute("SYNOBJECT ");
}
}
|
這樣,當Web應用程序啟動時,容器會調用監聽器,從而設置共享對象。共享對象的訪問只需直接調用getAttribute方法即可。
多次設置、多次讀取
對于多次設置、多次讀取的共享對象,必須利用Java的同步機制,訪問步驟如下:
第一步,首先設計一個同步類。這個同步類的代碼非常簡單:
package org.i18.utils
public final class SynObject{
}
|
以上代碼可以看出,它其實什么都沒做,但繼承了Object關于同步的方法和機制。
第二步,把這個類的實例放到應用對象中作為一個共享對象。可以看出這個同步對象屬于一次設置、多次使用的共享對象。在應用程序啟動事件監聽器中設置它,請參見前面的代碼。
第三步,設置共享對象。如果有對象需要整個應用程序共享,可以在Servlet的service中利用同步機制來設置:
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
ServletContext context= getServletContext();
//得到同步對象
Object obj = context.getAttribute("SYNOBJECT");
//獲取同步鑰匙
synchronized(obj){
//先檢查是否存在這個共享對象
Object obj2 = context.getAttribute("Attr");
if (obj2 == null) {
//不存在,設置共享對象
obj2 = new TestBean();
context.setAttribute("Attr",obj2);
}
}
}
|
第四步,讀取共享對象。當需要訪問多次設置、多次訪問的共享對象時,同樣需要利用同步機制,代碼如下:
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
ServletContext context= getServletContext();
//得到同步對象,因為這個對象是一次設置、多次讀取型的,不需要同步
Object obj = context.getAttribute("SYNOBJECT");
//獲取同步鑰匙
synchronized(obj){
//得到需要的共享對象
Object obj2 = context.getAttribute("Attr");
}
}
|
會話對象內共享對象的多線程問題可以和應用對象一樣處理。請求對象和頁面對象正常情況下是線程安全的,除非開發者自己引入了額外的線程。
本文分析了開發好的J2EE應用必須掌握的、組件間對象共享的技術,這種分析技術同樣適用于EJB中。在EJB容器中,信息存放的位置變成了實現JNDI的服務提供者,大家通過JNDI的接口方法查詢、綁定共享對象。需要注意的是,同步對象不能在JNDI中實現,因為大家搜索出來的不是同一個內存對象。
|