題注:目前servlet和jsp是用來(lái)開(kāi)發(fā)web應用程序最流行的工具之一,本文由權威的servlet專(zhuān)家Jason Hunter撰寫(xiě),全面的而準確的介紹了從Servlet API 2.2 到 2.3(目前)的變化和原因,并展示了在servlets中如何使用filter的激動(dòng)人心的新技術(shù)。
翻譯者加:雖然Servlet2.4的規范即將出臺,但相信此篇文章對于那些剛剛開(kāi)始運用Servlet的愛(ài)好者們還會(huì )有所幫助。(鑒于此篇文章是在Servlet 2.3規范正式出臺之后轉譯,故有關(guān)Filter的部分未按原文,而是按照2.3最終規范中的修改的部分所翻譯)
英文原文
改變:
· 需要JDK1.2或更高版本
· 增加FILTER機制
· 增加應用程序生命周期事件
· 增加新的國際化支持
· 增加有關(guān)inter-JAR依賴(lài)關(guān)系的規定
· 澄清了加載類(lèi)的規則
· 增加新的錯誤和安全屬性
· 廢棄HttpUtils類(lèi)
· 增加了各種各樣有用的方法
· 擴展和澄清了幾個(gè)DTD行為
· 其他有關(guān)Server開(kāi)發(fā)商的有關(guān)說(shuō)明改動(dòng)
與J2EE的關(guān)系
· Servlet API 2.3成為J2EE 1.3的核心API
· 在web.xml中定義了J2EE相關(guān)的deployment descriptor
· <resource-env-ref>標記被用來(lái)支持如Java Messaging System (JMS)所必須的"administered objects"
· <res-ref-sharing-scope>標記規定了對于資源引用的訪(fǎng)問(wèn)方式
· <run-as>規定了EJB調用者的安全特性
· 大部分的Servlet開(kāi)發(fā)者不需要關(guān)心這些J2EE標記
過(guò)濾器(filters)
Servlet API 2.3中最重大的改變是增加了filters,filters能夠傳遞request或者修改response。 Filters并不是servlets;它們不能產(chǎn)生response。 你可以把過(guò)濾器看作是還沒(méi)有到達servlet的request的預處理器,或從servlet發(fā)出的response的后處理器。 一句話(huà),filter代表了原先的"servlet chaining"概念,且比"servlet chaining"更成熟。 filter能夠:
· 在servlet被調用之前截取servlet
· 在servlet被調用之前查看request
· 提供一個(gè)自定義的封裝原有request的request對象,修改request頭和request內容
· 提供一個(gè)自定義的封裝原有response的response對象,修改response頭和response內
· 在servlet被調用之后截取servlet
· 對serlvet進(jìn)行分組管理;每個(gè)servlet或每組servlet可以配置多個(gè)filters
javax.servlet.Filter實(shí)現了以下方法:
· public void init(FilterConfig filterConfig)throws ServletException:
初始化操作
· public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws java.io.IOException,ServletException:執行實(shí)際的過(guò)濾的操作
· public void destroy():釋放資源
每個(gè)request和response都會(huì )傳到filter的doFilter()方法中, 也就是說(shuō)在FilterChain對象中包含著(zhù)所有將被執行的filters。一個(gè)filter可以在doFilter()方法中對request和response進(jìn)行處理。 (它能夠通過(guò)調用其他的方法獲得數據,或賦予獲得的對象新的行為。) 然后,當前的filter調用chain.doFilter()方法把控制權傳遞給下一個(gè)filter。 在調用返回時(shí), filter能夠在doFilter()方法結束的地方,對response進(jìn)行附加的處理,例如,對response進(jìn)行日志管理。如果filter想停止request處理過(guò)程并獲得對response的完全控制,則不要去調用下一個(gè)filter。
filter能夠通過(guò)包裝request和/或response對象來(lái)提供自定義的行為,改變某個(gè)方法的調用實(shí)現影響以后的request操作動(dòng)作.API 2.3提供了新的HttpServletRequestWrapper和HttpServletResponseWrapper類(lèi)來(lái)協(xié)助實(shí)現;這兩個(gè)類(lèi)提供了所有request和response方法的缺省實(shí)現, 并且缺省地代理了所有對原有request和response的調用。這意味著(zhù)要改變一個(gè)方法的行為只需要從封裝類(lèi)繼承并重新實(shí)現一個(gè)方法即可。封裝類(lèi)在處理request和生成response方面為filters提供了極大的控制方式。對所有的request的執行過(guò)程進(jìn)行記錄的一個(gè)簡(jiǎn)單的日志過(guò)濾器的例子代碼如下:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LogFilter implements Filter {
FilterConfig config;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws ServletException, IOException {
ServletContext context =config.getServletContext();
long bef = System.currentTimeMillis();
chain.doFilter(req, res); // no chain parameter needed here
long aft = System.currentTimeMillis();
context.log("Request to " + ((HttpServletRequest)req).getRequestURI() + ": " + (aft-bef));
}
public void init (FilterConfig config) throws ServletException{
this.config = config;
}
public void destroy () {
this.config = null;
}
}
當server調用init()方法時(shí), filter把config的引用保存到config變量中, 在后面用到的doFilter()方法中將使用這個(gè)變量獲得ServletContext.。doFilter()方法中的實(shí)現很簡(jiǎn)單:對request進(jìn)行計時(shí)并在處理完成的時(shí)候記錄下來(lái)。 要使用filter, 必須在web.xml文件中定義<filter>標記, 如下所示:[pre]
<filter>
<filter-name>log</filter-name>
<filter-class>LogFilter</filter-class>
</filter>[/pre]
上述定義告訴server名字為log的filter的實(shí)現類(lèi)是LogFilter類(lèi),然后使用<filter-mapping>標記把注冊過(guò)的filter與特定的URL映射模式或servlet名對應起來(lái):
[pre]
<filter-mapping>
<filter-name>log</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>[/pre]
這個(gè)配置使filter對所有發(fā)送到server的請求起作用(包括靜態(tài)請求和動(dòng)態(tài)請求),達到了日志過(guò)濾器的作用。 當你訪(fǎng)問(wèn)一個(gè)簡(jiǎn)單的頁(yè)面上時(shí),輸出的日志有可能如下:
Request to /index.jsp: 10
生命周期事件
Servlet API 2.3第二個(gè)意義重大的改變是增加了應用程序生命周期事件,即當servlet contexts和sessions被初始化或釋放時(shí),當向context或session上添加或刪除屬性的時(shí)候,通知"listener"對象.
Servlet生命周期事件與Swing的事件機制類(lèi)似。對ServletContext生命周期進(jìn)行觀(guān)察的監聽(tīng)器必須實(shí)現 ServletContextListener接口。這個(gè)接口有兩個(gè)方法:
· void contextInitialized(ServletContextEvent e):當Web application第一次準備處理requests的時(shí)候(例如:Web server啟動(dòng)并且context被加載和重新加載時(shí))。 本方法返回之前不開(kāi)始處理Requests
· void contextDestroyed(ServletContextEvent e):在Web application要被關(guān)閉的時(shí)候(例如:當Web server關(guān)閉或當一個(gè)context被移去或重新加載的時(shí)候)。本方法調用時(shí)Request處理已經(jīng)停止.
傳入這些方法的ServletContextEvent類(lèi)只有一個(gè)返回被初始化或被移去的context的getServletContext()方法。
對ServletContext屬性生命周期進(jìn)行觀(guān)察的監聽(tīng)器必須實(shí)現ServletContextAttributesListener接口,這個(gè)接口有三個(gè)方法:
· void attributeAdded(ServletContextAttributeEvent e):當屬性被加到servlet context上時(shí)調用
· void attributeRemoved(ServletContextAttributeEvent e):當屬性被從servlet context上移走時(shí)調用
· void attributeReplaced(ServletContextAttributeEvent e):當servlet context上的屬性被另一個(gè)屬性所代替的時(shí)候調用
繼承于ServletContextEvent的ServletContextAttributeEvent類(lèi),增加了getName()和getvalue()方法,這兩個(gè)方法使監聽(tīng)器得到正在被改變的屬性的相關(guān)信息?,F在對于需要同步應用程序狀態(tài)(context屬性)的應用程序來(lái)說(shuō),如數據庫處理,能夠方便的在這個(gè)地方進(jìn)行統一的處理。
session listener model與context listener model類(lèi)似。在session事件模型中, 有一個(gè)擁有兩個(gè)方法的HttpSessionListener接口:
· void sessionCreated(HttpSessionEvent e):當session創(chuàng )建時(shí)被調用
· void sessionDestroyed(HttpSessionEvent e):當session被釋放時(shí)或無(wú)效時(shí)被調用
這些方法接受一個(gè)擁有返回被創(chuàng )建或被釋放的session的getSession()方法的HttpSessionEvent類(lèi)實(shí)例??梢允褂眠@些方法實(shí)現一個(gè)對Web application中所有的活動(dòng)用戶(hù)進(jìn)行跟蹤的管理者接口。
session事件模型還有一個(gè)HttpSessionAttributesListener接口,這個(gè)接口有三個(gè)方法。這些方法告訴監聽(tīng)器屬性何時(shí)改變,何時(shí)能夠被使用:
· void attributeAdded(HttpSessionBindingEvent e):當屬性被加到session上時(shí)調用
· void attributeRemoved(HttpSessionBindingEvent e):當屬性被從session上移去時(shí)調用
· void attributeReplaced(HttpSessionBindingEvent e):當session上的屬性被另一個(gè)屬性所代替的時(shí)候調用
與你所猜想的一樣, HttpSessionBindingEvent類(lèi)繼承于HttpSessionEvent接口,并且加入了getName()和getvalue()方法. 唯一一點(diǎn)不一樣的地方是事件類(lèi)的名字是HttpSessionBindingEvent,而不是 HttpSessionAttributeEvent。合理的解釋是:原有的API已經(jīng)存在了一個(gè)名為HttpSessionBindingEvent的事件類(lèi), 因此這個(gè)事件類(lèi)被重用。
生命周期事件的一個(gè)實(shí)際應用由context監聽(tīng)器管理共享數據庫連接。在web.xml中如下定義監聽(tīng)器:[pre]
<listener>
<listener-class>com.acme.MyConnectionManager</listener-class>
</listener>[/pre]server創(chuàng )建監聽(tīng)器的實(shí)例接受事件并自動(dòng)判斷實(shí)現監聽(tīng)器接口的類(lèi)型。要記住的是由于監聽(tīng)器是配置在deployment descriptor中,所以不需要改變任何代碼就可以添加新的監聽(tīng)器。監聽(tīng)器的例子如下:
public class MyConnectionManager implements ServletContextListener {
public void contextInitialized(ServletContextEvent e) {
Connection con = // create connection
e.getServletContext().setAttribute("con", con);
}
public void contextDestroyed(ServletContextEvent e) {
Connection con = (Connection) e.getServletContext().getAttribute("con");
try { con.close(); } catch (SQLException ignored) { } // close connection
}
}
監聽(tīng)器保證每新生成一個(gè)servlet context都會(huì )有一個(gè)可用的數據庫連接,并且所有的連接對會(huì )在context關(guān)閉的時(shí)候 隨之關(guān)閉。
API 2.3新增的HttpSessionActivationListener監聽(tīng)器接口的目的是處理在server之間傳送的session。實(shí)現HttpSessionActivationListener的監聽(tīng)器在session要被鈍化(移動(dòng))和session在其它的主機上要被激活(開(kāi)始活動(dòng))的時(shí)候被通知。使用這個(gè)接口可以在JVM之間長(cháng)久保存沒(méi)有進(jìn)行序列化的數據,或者是在移動(dòng)session前后對序列化的對象執行一些附加的操作。這個(gè)接口有兩個(gè)方法:
· void sessionWillPassivate(HttpSessionEvent e):session將被鈍化。 此時(shí)session已經(jīng)超出了service的管理范圍
· void sessionDidActivate(HttpSessionEvent e):session已經(jīng)被激活。此時(shí)session還沒(méi)有進(jìn)入service的管理范圍
這個(gè)監聽(tīng)器接口與其他的監聽(tīng)器接口在原理上類(lèi)似。然而與其它監聽(tīng)器不同的是,鈍化和激活的調用很有可能發(fā)生在兩個(gè)不同的server中!
選擇字符編碼方式
API 2.3對處理不同語(yǔ)言的表單提交方式的迫切需要提供了新方法,request.setCharacterEncoding(String encoding),告訴server一個(gè)request的字符編碼。字符編碼也叫做字符集,反映了字節到字符的映射模式。server能夠使用指定的字符集正確的解析參數和POST數據。缺省情況下,server使用常用的Latin-1(ISO 8859-1)字符集解析參數。然而這只能在使用西方和歐洲的語(yǔ)言的情況下正常工作。當瀏覽器使用其他的字符集時(shí),假設在request的Content-Type頭中傳送編碼信息,但是沒(méi)有多少瀏覽器這樣做。通過(guò)使用這個(gè)方法,servlet能夠通知server使用哪一種字符集,server只需關(guān)注其余部分。例如, servlet從以Shift_JIS編碼方式的表單接受并讀取Japanese參數的代碼如下:[pre]
// Set the charset as Shift_JIS
req.setCharacterEncoding("Shift_JIS");
// Read a parameter using that charset
String name = req.getParameter("name");[/pre]
記住在調用getParameter()方法或getReader()方法之前設置編碼。setCharacterEncoding()對于不支持的編碼會(huì )拋出java.io.UnsupportedEncodingException。
JAR dependencies
通常情況下,WAR文件(Web application歸檔文件,在A(yíng)PI 2.2中加入)需要各種JAR類(lèi)庫對server提供必要的支持和正確的操作。例如,一個(gè)使用ParameterParser類(lèi)的Web application需要在它的classpath存在cos.jar文件。使用WebMacro的Web application需要webmacro.jar文件。在A(yíng)PI 2.3之前,這些要用到的從屬文件必須在文檔中說(shuō)明(相當于每個(gè)人都要讀說(shuō)明文檔!) 或者是每個(gè)Web application不得不把它所需要的所有的jar文件包含在自身的WEB-INF/lib目錄中(完全無(wú)必要的使Web application變得膨脹起來(lái))。
在Servlet API 2.3中,可以通過(guò)WAR文件中的META-INF/MANIFEST.MF文件來(lái)表示從屬的jar文件與WAR文件之間的關(guān)系。 這是定義jar文件依賴(lài)關(guān)系的標準方法,但是在A(yíng)PI 2.3中,WAR 文件必須正式的支持上述的機制。如果一個(gè)規定的從屬條件不能夠被滿(mǎn)足,server能夠在拒絕Web application的發(fā)布,而不是在運行時(shí)刻產(chǎn)生晦澀難懂的錯誤消息。 The mechanism allows a high degree of granularity(不知如何翻譯)。例如,可以指定一個(gè)可選包的特定的版本作為Web application的從屬包,那么server就必須以一種算法找到所指定的那個(gè)包。
Class loaders
在這兒雖然只改動(dòng)了一點(diǎn)點(diǎn),但是影響很大:在A(yíng)PI 2.3中,一個(gè)servlet容器必須確保server的實(shí)現類(lèi)對于Web application所使用的類(lèi)是不可見(jiàn)的。換句話(huà)說(shuō),class loaders應該被獨立的保存起來(lái)。
這聽(tīng)起來(lái)好像沒(méi)什么,但是這樣做消除了Web application的classes和server classes之間可能存在沖突的機會(huì )。在使用XML解析器上這已經(jīng)成為一個(gè)嚴重的沖突問(wèn)題。每個(gè)server都需要一個(gè)XML解析器解析web.xml文件,并且許多Web applications也使用XML解析器對XML數據進(jìn)行讀,寫(xiě)或其他處理。如果解析器支持不同版本DOM或SAX,就可能導致無(wú)法挽回的沖突。規定不同的類(lèi)的獨立性很好的解決了這個(gè)問(wèn)題。
新的錯誤屬性
在Servlet API 2.2中引入了幾個(gè)request屬性,引入的屬性在滿(mǎn)足<error-page>規定的條件時(shí),能夠被Servlet和JSP所用到。它的作用是讓你可以在Web application中配置當出現特定的錯誤類(lèi)型和特定的異常類(lèi)型時(shí),可以顯示個(gè)用戶(hù)指定的頁(yè)面:[pre]
<web-app>
<!-- ..... -->
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<error-page>
<exception-type>javax.servlet.ServletException</exception-type>
<location>/servlet/ErrorDisplay</location>
</error-page>
<!-- ..... -->
</web-app>[/pre]
由<error-page>標記所規定的的在<location>標記中的servlet能夠使用下表的三個(gè)屬性:
· javax.servlet.error.status_code:錯誤類(lèi)型的整形值
· javax.servlet.error.exception_type:產(chǎn)生錯誤的異常類(lèi)的實(shí)例
· javax.servlet.error.message:異常信息字符串
使用這些屬性,servlet能夠自定義錯誤頁(yè)面,例:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ErrorDisplay extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
String code = null, message = null, type = null;
Object codeObj, messageObj, typeObj;
// Retrieve the three possible error attributes, some may be null
codeObj = req.getAttribute("javax.servlet.error.status_code");
messageObj = req.getAttribute("javax.servlet.error.message");
typeObj = req.getAttribute("javax.servlet.error.exception_type");
// Convert the attributes to string values
// We do things this way because some old servers return String
// types while new servers return Integer, String, and Class types.
// This works for all.
if (codeObj != null) code = codeObj.toString();
if (messageObj != null) message = messageObj.toString();
if (typeObj != null) type = typeObj.toString();
// The error reason is either the status code or exception type
String reason = (code != null ? code : type);
out.println("<HTML>");
out.println("<HEAD><TITLE>" + reason + ": " + message + "</TITLE></HEAD>");
out.println("<BODY>");
out.println("<H1>" + reason + "</H1>");
out.println("<H2>" + message + "</H2>");
out.println("<HR>");
out.println("<I>Error accessing " + req.getRequestURI() + "</I>");
out.println("</BODY></HTML>");
}
}
設想一下,如果錯誤頁(yè)面中能夠包含產(chǎn)生異常的堆棧信息或者包含導致問(wèn)題產(chǎn)生的servlet的URI(導致問(wèn)題產(chǎn)生的servlet的URI通常都不是最開(kāi)始進(jìn)行請求的那個(gè)URI),情況如何?在A(yíng)PI 2.2中是不可能的。而API 2.3中可以從以下表的兩個(gè)屬性獲得這些信息:
· javax.servlet.error.exception:實(shí)際的異常擲出的Throwable對象
· javax.servlet.error.request_uri:導致問(wèn)題產(chǎn)生的資源的URI字符串對象
這些屬性能夠讓錯誤頁(yè)面包含異常的堆棧信息和產(chǎn)生問(wèn)題的資源的URI。下面的servlet例子使用了新屬性。
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ErrorDisplay extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
String code = null, message = null, type = null, uri = null;
Object codeObj, messageObj, typeObj;
Throwable throwable;
// Retrieve the three possible error attributes, some may be null
codeObj = req.getAttribute("javax.servlet.error.status_code");
messageObj = req.getAttribute("javax.servlet.error.message");
typeObj = req.getAttribute("javax.servlet.error.exception_type");
throwable = (Throwable) req.getAttribute
("javax.servlet.error.exception");
uri = (String) req.getAttribute("javax.servlet.error.request_uri");
if (uri == null) {
uri = req.getRequestURI(); // in case there’s no URI given
}
// Convert the attributes to string values
if (codeObj != null) code = codeObj.toString();
if (messageObj != null) message = messageObj.toString();
if (typeObj != null) type = typeObj.toString();
// The error reason is either the status code or exception type
String reason = (code != null ? code : type);
out.println("<HTML>");
out.println("<HEAD><TITLE>" + reason + ": " + message
+ "</TITLE></HEAD>");
out.println("<BODY>");
out.println("<H1>" + reason + "</H1>");
out.println("<H2>" + message + "</H2>");
out.println("<PRE>");
if (throwable != null) {
throwable.printStackTrace(out);
}
out.println("</PRE>");
out.println("<HR>");
out.println("<I>Error accessing " + uri + "</I>");
out.println("</BODY></HTML>");
}
}
新的安全屬性
Servlet API 2.3增加了兩個(gè)新的request屬性,有助于一個(gè)使用HTTPS連接的servlet獲得有關(guān)的某些信息。對于使用HTTPS加密的servlet,server支持下面列出的新加的request屬性:
· javax.servlet.request.cipher_suite:HTTPS所使用的密碼組
· javax.servlet.request.key_size:加密算法所使用的密鑰長(cháng)度
servlet可以在程序中使用這些屬性決定是否一個(gè)網(wǎng)絡(luò )連接擁有足夠的安全強度。應用程序可以拒絕加密位數不夠長(cháng)或使用不信任算法的那些連接。例如,下面的方法判斷連接是否使用了不低于128位長(cháng)度的密鑰進(jìn)行加密。
public boolean isAbove128(HttpServletRequest req) {
Integer size = (Integer) req.getAttribute("javax.servlet.request.key_size");
if (size == null || size.intvalue() < 128) {
return false;
}
else {
return true;
}
}
細微的調整
在A(yíng)PI 2.3中還存在一些細微的調整之處。首先,在HttpServletReques類(lèi)中定義了新的四個(gè)靜態(tài)的不可變的字符串型的常量:BASIC_AUTH,DIGEST_AUTH,CLIENT_CERT_AUTH,和FORM_AUTH。與之對應的是用來(lái)獲得客戶(hù)端使用哪一種認證方式的getAuthType()方法的代碼可以如下書(shū)寫(xiě):
if (req.getAuthType() == req.BASIC_AUTH) {
// handle basic authentication
}
與API 2.2兼容,原有的判斷方式同樣能夠使用,但是不符合代碼的規范性。注意將"BASIC"放在equals()之前能夠避免在getAuthType()方法返回null的時(shí)候產(chǎn)生空指針異常:
if ("BASIC".equals(req.getAuthType())) {
// handle basic authentication
}
另一個(gè)API 2.3中的改變是HttpUtils類(lèi),這個(gè)類(lèi)不再被推薦使用。HttpUtils可以被認為是一組靜態(tài)方法的集合,這些方法有時(shí)非常有用,但是可以把他們放到其他更合適的地方。 API 2.3把這些功能移到了request對象中,顯得更為合理。request對象中的新方法如下:
· StringBuffer req.getRequestURL():返回一個(gè)從request信息中獲得的包含原始· request URL的StringBuffer對象
· java.util.Map req.getParameterMap():返回一個(gè)包含request參數的不可變的Map對象。參數名和參數值分別對應Map對象中的關(guān)鍵字和值。但是不能確定擁有多個(gè)參數值的參數的處理方式,很可能,所有的值由一個(gè)String[]對象表示。
這些方法使用新加的req.setCharacterEncoding()方法來(lái)處理字符編碼轉換。
API 2.3同樣增加了兩個(gè)關(guān)于ServletContext的新方法,這兩個(gè)新方法可以得到context的名字和context的資源列表
· String context.getServletContextName():返回在web.xml中定義的context的名字
· java.util.Set context.getResourcePaths():返回context中所有可用的資源路徑,返回值類(lèi)型為一個(gè)包含String對象的不可變的Set對象。每個(gè)String對象的內容以斜線(xiàn)(’/’)開(kāi)始并相對于context的根目錄
response對象也增加了一個(gè)新的方法,這個(gè)方法增加了對response buffer的程序上的控制。API 2.2中引入了res.reset()方法重置response和清空response的內容,頭和狀態(tài)碼。API 2.3添加了res.resetBuffer()方法只用來(lái)清空response內容:
· void res.resetBuffer():清空response buffer,不清空頭和狀態(tài)碼。如果response已經(jīng)被輸出,擲出IllegalStateException異常。
最后,在一組專(zhuān)家們長(cháng)期的辯論后,Servlet API 2.3確定了當一個(gè)不在context根目錄的servlet調用res.sendRedirect("/index.html")的時(shí)候,將會(huì )產(chǎn)生什么行為。在Servlet API 2.2 中對一個(gè)不完整的路徑如"/index.html"發(fā)出請求,將會(huì )被servlet container轉換成一個(gè)完整的路徑,但是沒(méi)有說(shuō)明context路徑是如何被轉換的。如果context中的servlet要訪(fǎng)問(wèn)的路徑是"/index.html",redirect URI將這個(gè)路徑轉換成相對容器的根目錄(http://server:port/index.html),還是相對于context的根目錄(http://server:port/contextpath/index.html)呢?從最大的可移植性方面考慮,這個(gè)行為必須被確定下來(lái);在爭論了很長(cháng)時(shí)間之后,專(zhuān)家們選擇了將其轉換化成相對于容器根目錄的方案。對于那些堅持相對于context的人,可以使用getContextPath()方法獲得相對于context的URL結果。
DTD的修正
最后,Servlet API 2.3增加了對web.xml deployment descriptor行為的一些要求。在使用web.xml文件之前,必須除去文本值兩端的空格。(在不需要進(jìn)行校驗的標準XML中,通常情況下保留所有的空格。)這個(gè)規定保證了如下的兩個(gè)條目以相同的方式被進(jìn)行處理:
<servlet-name>hello<servlet-name>
和
<servlet-name>
hello
</servlet-name>
API 2.3提供了<auth-constraint>標記規則,特殊值"*"作為<role-name>的通配符表示允許所有的roles訪(fǎng)問(wèn)。如下的xml定義表示當前Web application中所有的用戶(hù)只要屬于Web application中所定義任何一個(gè)role,就可以對響應的web資源進(jìn)行訪(fǎng)問(wèn):[pre]
<auth-constraint>
<role-name>*</role-name> <!-- allow all recognized roles -->
</auth-constraint>[/pre]
最后,澄清了在isUserRole()方法中所用到的參數問(wèn)題,參數可以使用在<security-role>中定義的角色。 例,在web.xml實(shí)體中定義的腳本片段:[pre]
<servlet>
<servlet-name>secret</servlet-name>
<servlet-class>SalaryViewer</servlet-class>
<security-role-ref>
<role-name>mgr<!-- name used by servlet --></role-name>
<role-link>manager<!-- name used in deployment descriptor --></role-link>
</security-role-ref>
</servlet>
<!-- ... -->
<security-role>
<role-name>manager</role-name>
</security-role>[/pre]
無(wú)論servlet調用isUserInRole("mgr")還是調用isUserInRole("manager")方法,所獲得的結果是一致的。從根本上說(shuō),security-role-ref作用是生成一個(gè)別名,但并不是必須的。雖然這顯得好像本來(lái)就應該如此,但是API 2.2規范中敘述到只能夠使用那些明確在<security-rike-ref>別名規則中定義的角色。
Servlet API 2.3的變更
新增加的類(lèi)
Filter, FilterChain, FilterConfig, ServletContextAttributeEvent, ServletContextAttributesListener, ServletContextEvent, ServletContextListener, ServletRequestWrapper, ServletResponseWrapper, HttpServletRequestWrapper, HttpServletResponseWrapper, HttpSessionActivationListener, HttpSessionAttributesListener, HttpSessionEvent, HttpSessionListener
新增加的方法
getResourcePaths(), getServletContextName(), resetBuffer(), HttpSessionBindingEvent.getvalue()
新增加的屬性
BASIC_AUTH, DIGEST_AUTH, CLIENT_CERT_AUTH, 和FORM_AUTH
不再被推薦使用的類(lèi)
HttpUtils