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