js|servlet
即將面世的J2EE 1.4提供用Java開發Web應用程式的新的Servlet 2.4和JavaServer Pages (JSP) 2.0技術。本文展示了這兩種技術的新特性,並在適當的地方提供每個特性的範例程式碼。本文假設讀者熟悉以前的 Servlet 2.3和JSP 1.2版本。給出的例子已用Tomcat 5(包含在Java Web Services Developer Pack 1.2中)進行了測試。
Servlet和JSP毫無疑問是兩種應用最廣的J2EE技術。Servlet技術是用Java進行Web應用編程的基礎,也是JSP的基礎。但是,servlet編程可能會非常麻煩。特別是當你不得不發送一個沒多少代碼的長HTML頁面時更是如此。每個HTML標記必須嵌入到字串中,用PrintWriter對象的顯示方式發送。是一種工作單調乏味而煩人的工作。使用servlet的另一個缺點是每一處改變都需要servlet程式員介入。
Sun公司瞭解到這一問題之後便開發了JSP作為解決方案。在JSP中,程式員和頁面設計員的分工變得容易多了,並且當JSP頁面更改時會自動進行編譯。不過請注意,JSP是servlet技術的一個擴充,而不是廢棄servlet。在實際應用當中,servlet和JSP頁面一起使用。
Servlet 2.4的新特性
Servlet 2.4提供了幾個新類,且不支援javax.servlet.SingleThreadModel介面。這一版本只支援HTTP 1.1,所以Servlet 2.4應用程式不適用於HTTP 1.0客戶程式。2.4版增加了請求監聽器和請求屬性監聽器,並能在一個應用程式中將servlet用作歡迎頁面。另外,Servlet 2.4還提供了更好的ServletRequest和RequestDispatcher對象,並更好地支援國際化。此外,現在是根據模式而不是文件類型定義(document-type definition,DTD)檔案來驗證部署描述符是否有效。這就意味著支援部署描述符的可擴充性。
下面具體說明Servlet 2.4的新特性。請求監聽器和請求屬性監聽器。Servlet 2.3增加了servlet上下文相關監聽器和會話相關監聽器。Servlet 2.4增加了新的javax.servlet.ServletRequestListener和javax.servlet.ServletRequestAttributeListener兩種介面,它們會通知你與Request對象有關的事件。如果你對每個Request對象的初始化和撤消感興趣,你可以實施ServletRequestListener介面。這個介面有兩個方法:requestInitialized()和requestDestroyed()。當需要一個Request對象時,servlet容器便調用requestInitialized方法。當不再需要Request對象時,servlet容器便調用requestDestroyed方法。
這兩個方法都從servlet容器接收一個javax.servlet.ServletRequestEvent對象。可以從ServletRequestEvent執行個體獲得servlet上下文和servlet請求。
第二個監聽器介面ServletRequestAttributeListener處理Request對象屬性的添加、更改和刪除。該介面有以下方法:
- attributeAdded。向Request對象添加新屬性時由servlet容器調用。
- attributeRemoved。從Request對象中刪除屬性時由servlet容器調用。
- attributeReplaced。Request對象中現有屬性值被替換時由servlet容器調用。
這三個方法從servlet容器獲得javax.servlet.ServletRequestAttributeEvent類的一個執行個體。ServletRequestAttributeEvent類擴充了ServletRequestEvent類,並添加了兩個新方法:getName和getValue。getName方法返回觸發事件的屬性的名稱,getValue返回屬性的值。
代碼清單1 給出這兩個新的監聽器的樣本類。當servlet容器調用方法時二者都顯示方法名。監聽器經過編譯後,它們的類檔案必須被部署到WEB-INF/classes目錄下。ServletRequest中的新方法。在Servlet 2.4中,javax.servlet.ServletRequest介面增加了4個新方法:
- getRemotePort。返回傳送請求的客戶機或最後一個Proxy 伺服器的Internet Protocol(IP)源連接埠。
- getLocalName。返回從中接收請求的IP介面的主機名稱。
- getLocalAddr。返回從中接收請求的介面的IP地址。
- getLocalPort。返回從中接收請求的介面的IP連接埠號碼。
請注意,在Servlet 2.3中,getServerName和getServerPort方法返回的值就是現在getLocalName和getLocalPort返回的值。在2.4版中,getServerName和getServerPort已重新定義。欲瞭解更多的資訊,請查看API文檔。
將一個JSP頁面中的程式碼範例如下--
out.println("<br>Remote Port : " + request.getRemotePort());out.println("<br>Local Name : " + request.getLocalName());out.println("<br>Local Addr : " + request.getLocalAddr());out.println("<br>Local Port : " + request.getLocalPort());
--該代碼產生這樣的內容:
Remote Port : 3303 Local Name : localhost Local Addr : 127.0.0.1 Local Port : 8080
請求發送器的新特性。使用請求發送器可將當前請求傳遞給一個新的資源,或從當前頁面引入另一個資源。Servlet 2.4增加了一些屬性,它們將被添加到傳遞給另一個資源的一個Request對象上:
javax.servlet.forward.request_urijavax.servlet.forward.context_pathjavax.servlet.forward.servlet_pathjavax.servlet.forward.path_infojavax.servlet.forward.query_string
如果一個Request對象未被傳遞,則這些屬性的值為null。另一方面,在所傳遞來對象的資源中這些屬性將具有非null值。當某一個資源必須只能通過另一個資源調用而不能直接調用時,這些屬性值很有用。
舉個例子,在一個叫做myApp的Context(上下文)中有一個名為ModernServlet的servlet, ModernServlet被傳遞給TargetServlet。 在TargetServlet中,顯示代碼清單2中的代碼。
myApp的部署描述符包含以下和元素:
<servlet> <servlet-name>Modern</servlet-name> <servlet-class>ModernServlet </servlet-class></servlet><servlet-mapping> <servlet-name>Modern</servlet-name> <url-pattern>/Modern</url-pattern> </servlet-mapping><servlet> <servlet-name>Target</servlet-name> <servlet-class>TargetServlet </servlet-class></servlet><servlet-mapping> <servlet-name>Target</servlet-name> <url-pattern>/Target</url-pattern></servlet-mapping>
下面是調用ModernServlet時控制台顯示的結果:
javax.servlet.forward.request_uri : /myApp/Modernjavax.servlet.forward.context_path : /myAppjavax.servlet.forward.servlet_path : /Modernjavax.servlet.forward.path_info : nulljavax.servlet.forward.query_string : null
將過濾器用於請求發送器。Servlet 2.4在部署描述符中添加了一個新的元素,以便servlet程式員決定是否將過濾器(filters)應用於請求發送器。元素的值可以是REQUEST(預設值)、FORWARD、INCLUDE和ERROR:
- REQUEST。如果請求直接來自客戶機則使用過濾器。
- FORWARD。如果請求正由請求發送器進行處理,表示與或相匹配的Web組件使用傳遞調用,則使用過濾器。
- INCLUDE。只有在請求正由請求發送器進行處理,表示與或相匹配的Web組件使用包含(include)調用時,才使用過濾器。
- ERROR。只有在請求正由錯誤頁面機制處理為一個與元素相匹配的錯誤資源時才使用過濾器。
Servlet 2.4隻支援HTTP 1.1客戶機。Servlet 2.3既支援HTTP 1.0,又支援HTTP 1.1,而Servlet 2.4與Servlet 2.3不同,它只支援HTTP 1.1客戶機。作為過渡,HTTP/1.0狀態代碼302(暫時建議)仍然存在,而且仍然由javax.servlet.http.HttpServletResponse介面中的SC_MOVED_TEMPORARILY表示。HTTP 1.1具有Found的狀態代碼302,它由HttpServletResponse介面中的靜態SC_FOUND表示。
Servlet用作歡迎頁面。在Servlet 2.3中,你可以在部署描述符中使用元素列出歡迎檔案--當收到一個不完整的URL時將顯示的檔案。但是,在Servlet 2.3中,在元素中只能使用HTML檔案或JSP檔案。在Servlet 2.4中,如今可以將一個servlet用作歡迎頁面。下例為一個叫做Modern的servlet,它的類為ModernServlet.class,並已被映射到path /Modern。
<servlet> <servlet-name>Modern</servlet-name> <servlet-class>ModernServlet </servlet-class></servlet><servlet-mapping> <servlet-name>Modern</servlet-name> <url-pattern>/Modern</url-pattern></servlet-mapping><welcome-file-list> <welcome-file>Modern</welcome-file></welcome-file-list>
此時,若使用者鍵入諸如http://domain/context/(不帶資源檔)的URL時,就會調用ModernServlet。
對國際化的新支援。在Servlet 2.3中,沒有辦法直接告訴客戶瀏覽器應當使用什麼字元編碼。要實現這一目的,你必須把一個java.util.Locale對象傳遞給javax.servlet.ServletResponse介面的setLocale方法,如下所示:
response.setLocale(locale);
這意味著你必須首先建立一個Locale對象。
另外一種辦法是,在Servlet 2.3中,你可以使用setContentType方法來傳遞內容類型和字元集,如:
setContentType('text/html; charset=UTF-8');
在Servlet 2.4中,javax.servlet.ServletResponse介面中有兩個支援國際化的新方法。第一個方法是setCharacterEncoding,它的用法如下:
public voidsetCharacterEncoding(String charset)
使用setCharacterEncoding,你可以只將字元編碼指定為一個字串,而不必先建立Locale對象。不過,請注意,要讓這種方法起作用,必須在調用getWriter方法之前以及響應提交之前調用它。
第二個新方法是getContextType,作為在ServletResponse對象中調用setContentType、setLocale或setCharacterEncoding方法的結果,它返回在ServletResponse對象中使用的內容類型。
除了javax.servlet.ServletResponse中的這兩個方法之外,你還可以利用Servlet 2.4在部署描述符中定義一個新元素:它使servlet程式員不必在他/她的servlet中指定locale-to-charset映射。如何使用這一新元素的例子如下:
<locale-encoding-mapping-list> <locale-encoding-mapping> <locale>ja</locale> <encoding>ISO-2022-JP</encoding> </locale-encoding-mapping></locale-encoding-mapping-list>
部署描述符的可擴充性。在Servlet 2.3應用程式中,根據DTD檔案對部署描述符進行驗證。現在Servlet 2.4支援根據模式對部署描述符進行驗證。使用模式比使用DTD有以下幾點好處:
- 通過模式可以繼承另一個模式(可擴充的)的文法。
- 模式比DTD更精確。
- 通過模式可以指定每個元素的內容的實際資料類型。
- 模式可以用於多個名字空間。
- 通過模式可以指定一個元素出現的最多和最少次數。
但是,為了向後相容,要求Servlet 2.4容器支援Servlet 2.3和Servlet 2.2 DTD。
不支援javax.servlet.SingleThreadModel介面。SingleThreadModel介面沒有方法,它用於向servlet容器指明,它必須保證不會有兩個線程同時執行實施該介面的servlet的服務方法。從servlet技術開始出現到現在,人們普遍誤解了這個介面。現在大家都反對用它,因為它會造成混亂,並且在考慮安全執行緒時在安全性方面給servlet程式員一個錯覺。在任何新的開發工作中決不應再使用這個介面。
JSP 2.0中的新特性
JSP 2.0(最初稱為JSP 1.3)比JSP 1.2有了重要改進。當然,增加的最重要內容是JSP 2.0容器中加入了對錶達式語言(EL)的支援。
EL最初是由JSP標準標記庫(JSTL)1.0規範定義的,它可協助從JSP頁面中刪除Java代碼。javax.servlet.jsp.el包中所描述的API揭示EL的語義。EL運算式的語義與Java運算式的語義類似;運算式的值計算出來後被插入到當前的輸出中。EL可用於標準的或定製的操作的屬性值以及模板文本中。下面是EL運算式的結構(其中expr為運算式):
${expr}
對於包含字元序列"${"的文字值,JSP 2.0提供了一種方法,通過使用序列"${'${'"進行換碼。例如,下面的字元序列被轉換為文字值${expr}:
${'${'}expr}
此外,由於JSP 2.0以前的版本不支援EL,所以JSP應用程式將忽略任何Web應用程式中的EL,這些應用程式的web.xml根據Servlet 2.2或Servlet 2.3 DTD進行驗證。為了測試此處講到的JSP頁面中的運算式,你只需從應用程式中刪除web.xml檔案。
實際上,EL是一種簡單的語言,它協助頁面創作者訪問JSP隱含對象,進行反覆操作以及不包含Java代碼的條件操作--這些在JSP 1.2中是無法實現的。
為了訪問隱含對象,JSP容器支援下面的名稱-對象映射:
- pageContext。PageContext對象
- pageScope。將頁面範圍的屬性名稱映射到它們的值
- requestScope。將請求範圍的屬性名稱映射到它們的值
- sessionScope。將會話範圍的屬性名稱映射到它們的值
- applicationScope。將應用程式範圍的屬性名稱映射到它們的值
- param。將參數名映射到一個單一串參數值
- paramValues。將參數名映射到該參數所有值的一個字串數組
- header。將標題名映射到一個單一串標題值
- headerValues。將標題名映射到該標題所有值的一個字串數組
- cookie。將cookie名映射到一個單一cookie對象
- initParam。將上下文初始化參數名映射到其字串參數值
例如,下面的運算式表示參數userName的值:
${param.userName}
下面的運算式返回Session對象的productId屬性的值:
${sessionScope.productId}
更簡單的SimpleTag介面操作過程。JSP 2.0提供了一個新的介面javax.servlet.jsp.tagext.SimpleTag,它是編寫標記處理器(tag handler)的一種更簡單的方法。在JSP 1.2中,標記處理器必須直接或間接地實施avax.servlet.jsp.tagext包中的下列介面之一:Tag、IterationTag或BodyTag。對於實施Tag介面的標記處理器來說,最基本的情況是,JSP容器每次遇到JSP頁面中的一個標記時就調用doStartTag和doEndTag兩個方法。利用JSP 2.0,JSP程式員可以通過實施新的SimpleTag介面來選擇實施過程更簡單的標記處理器。JSP容器並不調用實施Tag介面的標記處理器的兩個方法,而只需要調用SimpleTag介面中的一個方法:doTag。所有標記邏輯、反覆操作和主體評估等都用這一個方法來執行。所以,SimpleTag與javax.servlet.jsp.tagext.BodyTag功能一樣強大,但操作過程更簡單。
為了支援需要實施SimpleTag介面的標記處理器的編寫,javax.servlet.jsp.tagext包提供了一個名為SimpleTagSupport的支援類。如果你要擴充這個類,則你只需提供一個執行方法:doTag。
代碼清單3給出了一個擴充SimpleTagSupport的標記處理器的例子。
使用標記檔案更輕鬆地開發標記庫。眾所周知,JSP 1.2中的自訂標籤庫需要花很多時間來開發。開發工作涉及標記處理器和標記庫描述符(TLD)檔案的開發,以及標記庫在web.xml檔案中的註冊。JSP 2.0通過提供一種新的編寫自訂標籤庫的方法解決了這個問題。使用標記檔案,標記延伸可類似於JSP檔案。無需編譯,無需編輯web.xml檔案,而且不再需要TLD。要做的是你必須把標記檔案複製到WEB-INF/ tags目錄中,而這一點很容易做到。剩下的事都交給JSP容器去做,它會把WEB-INF/tags目錄中找到的每個標記檔案轉換為標記處理器。程式員完全擺脫了構建標記處理器的複雜工作。
下面舉個例子。這是標記庫最簡單的形式,其中標記檔案只是簡單地把一個字串寫到隱含對象中。
<%— example1.tag file, must reside in WEB-INF/tags —%><% out.println("Hello from tag file.");%>
使用JSP頁面中的標記庫再簡單不過了。和平常一樣,你只需taglib指令,通過首碼屬性在整個頁面中識別標記庫。現在你有一個tagdir屬性,而不是uri屬性。tagdir屬性引用WEB-INF/tags目錄或WEB-INF/tags下的任何子目錄。
下面是一個使用example1.tag檔案的JSP頁面的例子。
<%@ taglib prefix="easyTag" tagdir="/WEB-INF/tags" %><easyTag:example1></easyTag:example1>
調用該JSP頁面瀏覽器上就會顯示下面的字串:
Hello from tag file.
結合上面講到的運算式語言,你就可以真正快速構建無指令碼的JSP頁面。再舉一個例子,下面的標記檔案(叫做example2.tag)通過調用JSP頁面接收一個屬性,並將它轉換為大寫字母。
<%— example2.tag file, must reside in WEB-INF/tags —%><%@ attribute name="x" %><% x = x.toUpperCase(); out.println(x);%>
下面是使用該標記檔案的JSP頁面:
<%@ taglib prefix="easyTag" tagdir="/WEB-INF/tags" %><easyTag:example2 x="hello"></easyTag:example2>
下面是另一個例子,其中沒有Java代碼:
<%— example3.tag file, must reside in WEB-INF/tags —%><%@ variable name-given="x" scope="AT_BEGIN" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><c:set var="x" value="3"/>After: ${x}<jsp:doBody/>
該標記檔案用於下面的JSP頁面:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="easyTag" tagdir="/WEB-INF/tags" %><c:set var="x" value="1"/>Before: ${x}<br><easyTag:example3/>
請注意,要運行本樣本,在WEB-INF/lib目錄下要有JSTL庫。
最後一個標記檔案樣本還表明,不熟悉Java程式設計語言的頁面創作者仍能利用標記延伸的強大功能。即便是Java程式員,使用標記檔案也比編寫實施javax.servlet.jsp.tagext包中的某個介面的Java類要方便。
結論
本文簡要闡述了Servlet 2.4和JSP 2.0規範中的新特性,它們將包含在即將面世的J2EE 1.4中。Servlet 2.4和JSP 2.0無疑將會加快Web應用程式的開發。