本期的 J2EE探索者是上個月的 正確處理會話作用域入門 的續篇。除了訪問會話作用域之外,JSP 隱式對象還可以用來處理 HTML 參數,轉發請求到一個 Web 組件,包括組件的內容、通過 JSP 容器的日志數據、控制輸出流,處理異常,等等。
本月,您將學到在 JSP 頁面中使用隱式對象。我們首先簡要概括 JSP 架構,其中包括了隱式對象。然後,我將介紹每個對象並描述它的核心功能。最後,我們將給出使用每種類型的對象和它提供的容器管理服務的一些最佳實踐。
隱式對象簡介
JSP 架構背後的理念是提供一個 Web 組件,它允許開發人員著重關注 Web 內容的表示,而不用陷入解析、編程和數據操縱等細節。JSP 應用程序本質上是特殊的 Web 組件,在處理用戶請求之前,J2EE Web 容器首先將其轉換成 servlet。在每個 JSP 應用程序內部有一套完整的隱式對象。
隱式對象使得開發人員可以訪問容器提供的服務和資源。這些對象之所以定義為 隱式的,是因為您不必顯式地聲明它們。不論您是否聲明它們――雖然您不能 重復聲明它們,它們在每個 JSP 頁面當中都進行定義,並且在後台由容器使用。因為隱式對象是自動聲明的,所以我們只需要使用與一個給定對象相關的引用變量來調用其方法。
9 個隱式對象及其功能的簡單描述如下:
Application 是使用范圍最廣的上下文狀態。它允許 JSP 頁面的 servlet 與包括在同一應用程序中的任何 Web 組件共享信息。
Config 允許將初始化數據傳遞給一個 JSP 頁面的 servlet。
Exception include 含有只能由指定的 JSP“error pages”訪問的異常數據。
Out 提供對 servlet 的輸出流的訪問。
Page 是JSP頁面的處理當前請求的 servlet 的實例。一般來說,JSP 頁面作者不使用該對象。
PageContext 是 JSP 頁面本身的上下文。它提供惟一一個 API 來管理具有不同作用域的屬性。這個 API 在實現 JSP 自定義標記處理程序時使用得非常多。
Request 提供對 HTTP 請求數據的訪問,同時還提供用於加入特定於請求的數據的上下文。
Response 允許直接訪問 HTTPServletResponse 對象,JSP 程序員很少使用該對象。
Session 可能是狀態管理上下文中使用得最多的對象。“會話”的概念是指單個用戶與 Web 應用程序在幾個請求上進行交互。
雖然有些隱式對象只提供單一的功能,但是幾個結合起來使用就可以提供多種功能。在接下來的一節裡,我們將按照功能分類來考察隱式對象:
會話管理: application , session , request , pageContext
流控制: application , config , pageContext , request , session
日志記錄和異常: application , config , exception , pageContext , request , session
輸入/輸出控制: request , response , out
初始化參數: config
會話管理
上個月我們提到過,為 JSP 定義的四個隱式對象可以用來在一個特定的上下文或者作用域中加入有狀態數據。這四個對象是 application 、 session 、 request 和 pageContext 。下表列出了這四個對象和它們定義的狀態上下文,同時還給出了對每個對象的簡單描述。
表1. JSP 狀態管理
隱式對象 作用域 描述 javax.servlet.ServletContext Application 代表整個運行時的 Web 模塊(應用程序)。作用域為 application 的數據在同一個應用程序模塊的所有 Web 組件之間共享。這很像J2EE 中提供的“全局(global)”數據 javax.servlet.http.HttpSession Session 代表當前的 HTTP 會話。除 page 作用域外, session 作用域是使用最普遍的上下文。這個對象在提供跨多個請求的持久的、有狀態的用戶體驗方面使用得最普遍 javax.servlet.http.HttpServletRequest Request 代表當前的 HTTP 請求。這個上下文可以跨越多個 Web 組件(servlet 和 JSP 頁面),只要這些組件屬於同一原子請求的一部分。由客戶機提供的特定於請求的數據(請求方法、URI、HTTP 參數等等)都被自動地保存在一個 request 上下文中。servlet 或 JSP 頁面還可以程式化地(programmatically)將數據的作用域指定為 request ,以便允許同一 request 作用域中的其他 servlet 或 JSP 頁面可以獲取該數據 javax.servlet.jsp.PageContext Page 代表當前 JSP 頁面的上下文。因為一個 JSP 頁面的上下文包括當前的請求、會話和應用程序,所以使用 pageContext 實例可以訪問與一個JSP 頁面相關的所有命名空間。它是所有對象的默認作用域,包括 JavaBeas 對象在內。 具有 page 作用域的對象通常會綁定到一個局部變量,以便在 scriptlet、表達式、JavaBeans 標記和自定義標記中可以訪問它
從最佳實踐的立場來看,我們應該盡可能地使用 page 作用域。它簡單,而且是 JSP 數據的默認作用域。 request 作用域非常適合於運行期間在組件間共享數據以處理一個特定的請求。 session 作用域被設計用來為單個用戶提供持久的、有狀態的體驗,它可以跨越多個請求。 application 作用域只有需要在組件之間跨用戶會話共享數據時才應該使用。參閱 參考資料以了解更多有關 session 作用域的信息。
流控制
面向對象設計方法的最大好處是可重用性。特別是,J2EE 系統將它們借用到模塊化風格的開發中,其中組件可以在其他應用程序中重新安排、重新打包和重新使用。即使您對設計可重用的 Web 模塊不感興趣,也很可能會發現您的 J2EE 應用程序由幾個部分組成。任何時候使用多個 servlet 或者 JSP 頁面(也就是組件)完成一個請求的時候,都需要使用某種類型的流控制技術。Servlet 架構提供兩種這樣的技術:forward(轉發) 和 include(包括)。
在 J2EE Web 開發中, forward會把處理用戶請求的控制權轉交給到其他 Web 組件。forward 在有些時候會比較有用,比如說需要用一個組件設置一些 JavaBean、打開或關閉資源、認證用戶,或者在將控制權傳遞給下一個組件之前需要執行一些准備工作。在轉發之前可以執行很多類型的任務,但是要轉發的組件不能設置響應頭部信息,也不能有內容發送到輸出緩沖區。所有與向客戶發送內容直接相關的任務必須由被轉發的組件完成。
J2EE 中第二種流控制技術是 include。在使用 forward 時,要傳遞控制權。與此不同的是,執行 include 的組件維持對請求的控制權,而只是簡單地請求將另一個組件的輸出包括在該頁面的某個特定的地方。對於常見的設計元素,例如頁首、頁腳和導航欄等,這是一個非常好的方法。
forward 和 include 都是通過一個專門的對象 java.servlet.RequestDispatcher 來完成的。簡單地調用一個 ServletContext 對象的 getRequestDispatcher() 方法就可以獲得一個 RequestDispatcher 對象。得到對 ServletContext 對象的引用有幾種方法,我們可以:
使用隱式聲明的 application 變量,因為它的類型本身已經是 ServletContext。
調用方法 getServletContext() ,該方法返回一個對隱式聲明的 application 變量的引用。
調用隱式聲明的 config 變量的 g etServletContext() 方法 。
調用隱式聲明的 pageContext 變量的 getServletContext() 方法 。
調用隱式聲明的 request 變量的 getServletContext() 方法 。
調用隱式聲明的 session 變量的 getServletContext() 方法 。
清單1給出了使用隱式變量 application 的 forward 流控制機制的代碼示例。
清單1. forward 流控制示例
javax.servlet.RequestDispatcher rd;
/* Obtain a reference to a RequestDispatcher object via the implicit
application variable*/
rd = application.getRequestDispatcher( "/NextPage.jsp" );
/* Perform the forward specified by the RequestDispatcher
and pass along a reference to the current request and
response objects */
rd.forward( request, response );
清單2給出了同樣使用變量 application 的 include 流控制的代碼示例。
清單2. include 流控制示例
javax.servlet.RequestDispatcher rd;
/* Obtain a reference to a RequestDispatcher object via the implicit
application variable*/
rd = application.getRequestDispatcher( "/Header.jsp" );
/* Perform the include specified by the RequestDispatcher
and pass along a reference to the current request and
response objects */
rd.include( request, response );
forward 和 include 是添加到 J2EE Web 開發工具包中的兩個非常棒的技術。還有其他一些方法可以在 JSP 頁面中完成 include,而且還有很多解決 J2EE 設計模式方面的文獻中講到了如何結合使用這兩種技術。參閱 參考資料以了解更多信息。
日志記錄和異常
如果您需要把與 Web 應用程序相關的信息存儲到一個日志中,依然有內建的方法可用。 ServletContext 接口聲明了兩個方法,用於把數據傳給一個日志。其中一個方法接受簡單的文本消息: log( java.lang.String ) ,另一個方法接受一個異常信息和一個文本消息: log(java.lang.Throwable, java.lang.String ) 。
在有了 ServletContext 接口提供的兩個可用的日志記錄方法之後,剩下的關鍵是獲取一個對 ServletContext 類型的對象的引用。像我們前面討論過的流控制對象一樣,有多種方法可以獲取對 ServletContext 類型的對象的引用。在獲得了對象引用之後,簡單地調用 log() 方法並向方法中傳遞必需的數據即可。一旦調用了這個方法,您當然就會希望能夠查看應用程序日志以查看消息。 ServletContext 是一個簡單的接口,並且也沒有規定怎樣實現它聲明的方法。因而 log 方法的具體實現是由供應商處理的。他們可以把日志信息存儲到一個文本文件、二進制文件、數據庫中,或者是供應商認為合適的其他格式中。您需要從服務器的文檔中得知存儲日志的位置。
雖然向一個日志文件發送消息相當有用,但是很多時候您可能還想在發生不可恢復的異常時顯示一個用戶友好的錯誤消息。要實現這一功能,您可以聲明,您的 JSP 頁面使用一個單獨的頁面來處理錯誤消息。這是在 JSP 頁面的任何地方通過包含下面的 page 指令實現的:
<%@ page errorPage="ErrorMessage.jsp"%>
如果在處理 JSP 頁面時有一個異常拋出的話,exception 對象就會立即通過隱式聲明的 exception 變量的方式拋給指定的錯誤頁面。
為了使一個 JSP 頁面能夠作為一個錯誤頁面,它必須包含一個指令來聲明這個頁面是指定用來處理錯誤的特殊頁面,指令如下:
<%@ page isErrorPage="true"%>
為了使用 ErrorMessage.jsp 頁面能夠作為一個錯誤頁面,這個指令必須出現在頁面的某個地方。錯誤頁面可以顯示一個友好的信息給用戶,然後可以將相關的異常信息寫入日志以供管理員日後查看。
輸入和輸出控制
因為 JSP 頁面僅僅是 HTTP servlet 的一個簡單抽象,所以您可以訪問 HttpServletRequest 和 HttpServletResponse 對象。如果需要特定於請求的信息,比如客戶機浏覽器的類型、HTTP post 的內容類型、客戶機性能、Cookie 數據或者請求參數,簡單地用隱式聲明的 request 變量直接調用適當的方法即可。類似地,如果您需要設置響應頭部信息,比如說浏覽器類型、內容類型、內容長度等等,簡單地用隱式變量 response 調用適當的方法即可。
如果需要直接訪問 JSP 頁面的輸出流,您可能會試圖通過隱式 response 變量調用 getWriter() 或 getOutputStream() 。然而由於 JSP 頁面的特殊性,您不能這樣做。如果需要直接訪問輸出流,必須通過一個 avax.servlet.jsp.JSPWriter 類型的特殊緩沖 PrintWriter 對象來訪問。怎樣定位這樣一個對象的引用呢?JSP 容器將會為您隱式地聲明一個,並通過 out 變量提供給您。在 JSP scriptlet 中可以通過簡單地調用 out.print() 或 out.println() 使用它。
一般來說不需要像這樣直接使用 JSPWriter 對象,而只需簡單地把內容作為普通文本或者通過 JSP 表達式寫入,然後允許容器將這些信息翻譯成 JSPWriter 調用。然而,在兩種情況下您需要直接使用 out 變量。一種情況是要為 JSP 自定義標記定義處理程序,這部分內容我們將在下個月重點講到。另外一種情況是您想要對 JSP 創建的輸出擁有更多的控制。如果您有一段夾雜著 JSP scriptlets 和表達式的 HTML,您可能會發現創建大的 scriptlet 然後在需要輸出內容到客戶機的時候使用 out.println() 語句這樣做會更簡潔、更容易。
初始化參數
如果您有一些靜態數據想提供給 JSP 頁面使用,並且那些數據不會頻繁地改動,初始化參數可能會是一個比較好的選擇。初始化參數有時候又叫環境變量或者“init”參數,這些參數通過位於一個 per-servlet/JSP 內的 Web 應用程序的 web.xml 文件指定,並且它們在servlet 的生命周期中只讀取一次,即在初始化時讀取。
清單3是一個初始化參數聲明的例子。
清單3. 初始化參數聲明
<webapp>
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.gabhart.MyTestServlet</servlet-class>
<init-param>
<param-name>contactEmail</param-name>
<param-value>[email protected]</param-value>
</init-param>
</servlet>
</webapp>
使用隱式變量 config 可以訪問這些參數的值,隱式變量 config 是對 JSP 頁面的 ServletConfig 對象的引用。通過 ServletConfig 接口提供了兩個處理 init 參數的方法。可以根據名字對一個特定的參數完成一個查找( getInitParameter( java.lang.String) ),或者也可以檢索到為 JSP 頁面定義的所有參數名字的一個 enumeration( getInitParameterNames() )。在擁有了enumeration 之後,可以通過循環查找每一個值。所有 init參數都是 String 對象。如果需要其他的數據類型,比如說整數、浮點數或者布爾值,必須使用相應的包裝器類來解析字符串。