http://www-900.ibm.com/developerWorks/cn/java/j-pj2ee7/index.shtml
淺淡 JSP 技術中使用最多的對象
Kyle Gabhart (kyle@gabhart.com)
顧問,Gabhart Consulting
2003年 9 月
接著上月對會話作用域的介紹,企業 Java 專家 Kyle Gabhart 深入研究了 JSP 隱式對象的多種用法。接下來,他將介紹 9 個隱式對象,解釋每個對象的用途(或者多種用途),最后給出一些怎樣在 JSP 編程中使用這些便利工具的最佳實踐。您可以到我們的 討論論壇 中分享您對這篇文章或者 J2EE 探索者 系列中的任何其他文章的想法。
本期的 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
變量的 getServletContext()
方法。
- 調用隱式聲明的
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>kyle@gabhart.com</param-value>
</init-param>
</servlet>
</webapp>
|
使用隱式變量 config
可以訪問這些參數的值,隱式變量 config
是對 JSP 頁面的ServletConfig
對象的引用。通過 ServletConfig
接口提供了兩個處理 init 參數的方法??梢愿鶕謱σ粋€特定的參數完成一個查找(getInitParameter( java.lang.String)
), 或者也可以檢索到為 JSP 頁面定義的所有參數名字的一個 enumeration(getInitParameterNames()
)。在擁有了enumeration 之后,可以通過循環查找每一個值。所有 init參數都是String
對象。如果需要其他的數據類型,比如說整數、浮點數或者布爾值,必須使用相應的包裝器類來解析字符串。
結束語
JSP 技術提供了 Servlet 架構之上的一個非常有用的抽象,它讓 Web 設計者可以著重關注內容表示,而只要求知道較少的編程細節。在本文中,您已經看到了我們是如何使用隱式對象來快速、容易地開發 Web 應用程序的。
下個月,我們將開始講述 JSP 自定義標記和 JSP 標準標記庫(JSTL)。學習自定義標記如何促進表示和業務邏輯之間的分離,同時還讓您可以將動態數據合并到表示層。到那時,一起快樂地探索吧!
參考資料
關于作者
Kyle Gabhart 是 J2EE、XML 和 Web 服務技術方面的獨立顧問和專家。Kyle 是一位很受歡迎的公眾演講者,以其對新技術的熱情、生動的分析和陳述而聞名。要獲取他最近以及即將舉行的演講或者業內出版物的信息,請訪問 Gabhart.com。您也可以通過 kyle@gabhart.com 與 Kyle 聯系。 |