Servlet規(guī)范簡介
引言
Web 框架一般是通過一個 Servlet 提供統(tǒng)一的請求入口,將指定的資源映射到這個 servlet, 在這個 servlet 中進(jìn)行框架的初始化配置,訪問 Web 頁面中的數(shù)據(jù),進(jìn)行邏輯處理后,將結(jié)果數(shù)據(jù)與的表現(xiàn)層相融合并展現(xiàn)給用戶。 WEB 框架想要在符合 Servlet 規(guī)范的容器中運(yùn)行,同樣也要符合 Servlet 規(guī)范。
將一個 WEB 框架注入到一個 servlet 中,主要涉及到 Servlet 規(guī)范中以下部分:
????????? 部署描述符
????????? 映射請求到 Servlet
????????? Servlet 生存周期
????????? 請求分發(fā)
?Servlet 相關(guān)技術(shù)規(guī)范簡介
部署描述符
部署描述符就是位于 WEB 應(yīng)用程序的 /WEB-INF 目錄下的 web.xml 的 XML 文件,是 WEB 應(yīng)用程序不可分割的部分,管理著 WEB 應(yīng)用程序的配置。部署描述符在應(yīng)用程序開發(fā)人員,應(yīng)用程序組裝人員,應(yīng)用程序部署人員之間傳遞 WEB 應(yīng)用程序的元素和配置信息。
在 WEB 應(yīng)用程序的部署描描述符中以下類型的配置和部署信息是所有的 servlet 容器必須支持的:
?????????
ServletContext
初始化參數(shù)
?????????
Session
配置
????????
Servlet
聲明
????????
Servlet
映射
????????
應(yīng)用程序生存周期監(jiān)聽器
?????????
Filter
的定義和映射
?????????
MIME
類型的映射
????????
歡迎文件列表
????????
錯誤文件列表
出現(xiàn)在部署描述符中的安全信息可以不被支持,除非這個
Servlet
容器是
J2EE
規(guī)范實現(xiàn)的一部分。
所有正確的
WEB
應(yīng)用程序部署描述符
(Servlet2.3
規(guī)范
)
必須包含下面的
DOCTYPE
聲明:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
|
下面說明在部署描述符中是如何進(jìn)行
Servlet
聲明和映射的,這個
DTD
的全部內(nèi)容可以在下面這個地址獲得:
http://java.sun.com/dtd/web-app_2_3.dtd
在這個
DTD
中有關(guān)
Servlet
聲明和映射和映射的部分如下:
<!--
The servlet element contains the declarative data of a
servlet. If a jsp-file is specified and the load-on-startup element
is present, then the JSP should be precompiled and loaded.
Used in: web-app
-->
<!ELEMENT servlet (icon?, servlet-name, display-name?, description?,
(servlet-class|jsp-file), init-param*, load-on-startup?, runas?,
security-role-ref*)>
<!--
The servlet-class element contains the fully qualified class name
of the servlet.
Used in: servlet
-->
<!ELEMENT servlet-class (#PCDATA)>
<!--
The servlet-mapping element defines a mapping between a servlet
and a url pattern
Used in: web-app
-->
<!ELEMENT servlet-mapping (servlet-name, url-pattern)>
<!--
The servlet-name element contains the canonical name of the
servlet. Each servlet name is unique within the web application.
Used in: filter-mapping, servlet, servlet-mapping
-->
<!ELEMENT servlet-name (#PCDATA)>
|
根據(jù)以上
DTD
,一個典型的
Servlet
的聲明的格式如下:
<servlet>
<servlet-name>catalog</servlet-name>
<servlet-class>com.mycorp.CatalogServlet</servlet-class>
<init-param>
<param-name>catalog</param-name>
<param-value>Spring</param-value>
</init-param>
</servlet> |
一個典型的Servlet映射如下:
<servlet-mapping>
<servlet-name>catalog</servlet-name>
<url-pattern>/catalog/*</url-pattern>
</servlet-mapping> |
通過上面的方法,我們就聲明了一個名稱為
catalog
的Servlet,它的實現(xiàn)類為com.mycorp.CatalogServlet,并且?guī)в幸粋€catalog參數(shù),參數(shù)值為Spring,所有向/catalog/*的請求都被映射到名稱為catalog的Servlet。
映射請求到 Servlet
接
收到一個請求后,WEB容器要確定轉(zhuǎn)到哪一個WEB應(yīng)用程序。被選擇的應(yīng)用程序的最長的上下文路徑必須和請求的URL開始部分匹配。URL匹配的部分是映射到Servlet的上下文路徑。
WEB
容器下一步必須按照下面的程序定位處理請求的Servlet。
用來映射到Servlet的路徑是請求對象的URL減去上下文的路徑。下面的URL路徑映射規(guī)則按順序執(zhí)行,容器選擇第一個成功的匹配并且不在進(jìn)行下一個匹配:
????????
容器試著對請求的路徑和Servlet的路徑進(jìn)行精確匹配,如果匹配成功則選擇這個Servlet。
????????
容器會循環(huán)的去試著匹配最長的路徑前綴:把’/’當(dāng)作路徑分隔符,按照路徑樹逐級遞減的完成,選擇最長匹配的Servlet。
????????
如果這個URL路徑的最后有擴(kuò)展名(比如.jsp),Servlet容器會試著匹配處理這個擴(kuò)展名的Servlet。
????????
如果前面的沒有與前面三條規(guī)則相匹配的Servlet,容器會試著為資源請求提供適當(dāng)?shù)馁Y源,如果有“默認(rèn)”的Servlet定義給這個應(yīng)用程序,那么這個Servlet會被使用。
容器必須使用一個大小寫敏感的匹配方式。
在部署描述符中,用下面的語法定義映射:
????????
一個以’/’開始并且以’/*’結(jié)束的字符串用來映射路徑。
????????
一個以’*.’為前綴的字符串用來映射擴(kuò)展名。
????????
一個只包含’/’的字符串指示著這個應(yīng)用程序“默認(rèn)”的Servlet,在這種情況下,servlet的路徑是請求的URI減去上下文路徑,并且這個路徑是null。
????????
所有其他的字符只用來精確匹配。
如果容器內(nèi)置JSP容器,那么*.jsp被映射到這個容器,并允許JSP頁面在需要的時候被執(zhí)行。這種映射叫做隱含映射。如果WEB應(yīng)用程序中定義了*.jsp的映射,那么這個映射有比隱含映射高的優(yōu)先級。
WEB
容器允許顯式的聲明隱含映射以獲得優(yōu)先級,例如,*.shtml的隱含映射可以在服務(wù)器上被映射為包含功能。
映射實例:
path pattern
|
servlet
|
/foo/bar/* |
servlet1 |
/baz/* |
servlet2 |
/catalog |
servlet3 |
*.bop |
servlet4 |
下面是實際請求映射的結(jié)果
incoming path |
servlet handling request |
/foo/bar/index.html |
servlet1 |
/foo/bar/index.bop |
servlet1 |
/baz |
servlet2 |
/baz/index.html |
servlet2 |
/catalog |
servlet3 |
/catalog/index.html |
“default” servlet |
/catalog/racecar.bop |
servlet4 |
/index.bop |
servlet4 |
請注意/catalog/index.html 和/catalog/racecar.bop這兩種情況,因為是精確匹配,所以并沒有映射到處理/catalog的servlet。
Servlet 生存周期
在介紹 Servlet 的生存周期之前需要先介紹一下 javax.servlet.Servlet 接口。所有的 Servlet 必須實現(xiàn)或者間接實現(xiàn)這個借口,我們通常可以通過繼承 javax.servlet.GenericServlet 或者 javax.servlet.http.HttpServlet. 類來實現(xiàn)這個接口。
這個接口中定義了下面 5 種方法:
public void init(ServletConfig config); public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res); public String getServletInfo(); public void destroy() ; |
init()
方法
init 方法在容器器裝入 Servlet 時執(zhí)行, Servlet 容器在實例化后只調(diào)用一次 init 方法, init 方法必須在 servlet 接收到任何請求之前完成。
這個方法通常用來進(jìn)行一些資源的管理和初始化,如從配置文件讀取配置數(shù)據(jù),讀取初始化參數(shù),初始化緩沖遲等一次性的操作。
getServletConfig()
方法
GetServletConfig 方法返回一個 ServletConfig 對象,該對象用來返回這個 Servlet 的初始化信息和啟動參數(shù)。返回的是傳遞到 init 方法 ServletConfig 。
Service()
方法
Service 方法是應(yīng)用程序邏輯的進(jìn)入點(diǎn),是 servlet 方法的核心, WEB 容器調(diào)用這個方法來響應(yīng)進(jìn)入的請求,只有 servlet 成功被 init() 方法初始化后, Service 方法才會被調(diào)用。
getServletInfo()
方法
這個方法返回一個字符串對象,提供有關(guān) servlet 的信息,如作者、版本等。
destroy()
方法
destroy 方法在容器移除 Servlet 時執(zhí)行,同樣只執(zhí)行一次。這個方法會在所有的線程的 service() 方法執(zhí)行完成或者超時后執(zhí)行,調(diào)用這個方法后,容器不會再調(diào)用這個 servlet 的方法,也就是說容器不再把請求發(fā)送給這個 Servlet 。 ????? 這個方法給 servlet 釋放占用的資源的機(jī)會,通常用來執(zhí)行一些清理任務(wù)。
這個接口定義了初始化一個 servlet, 服務(wù)請求和從容器中移除 servlet 的方法。他們按照下面的順序執(zhí)行:
1.???????? servlet 被實例化后,用 init 方法進(jìn)行初始化
2.???????? 客戶端的任何請求都調(diào)用 service 方法
3.???????? servlet 被移除服務(wù),調(diào)用 destroy 方法銷毀
servlet 的生存周期如下圖:
請求分發(fā)
請求分發(fā)可以讓一個Servlet把請求分配到另外一個資源,RequestDispatcher接口提供了實現(xiàn)他的機(jī)制??梢酝ㄟ^下面兩種方式從ServletContext中獲得一個實現(xiàn)了RequestDispatcher接口的對象:
? getRequestDispatcher
? getNamedDispatcher
getRequestDispatcher方法接受一個指向目標(biāo)資源的URL路徑
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); |
getNamedDispatcher方法接受一個Servlet名稱參數(shù),這個名稱是在部署描述符中<servlet-name>元素指定的那個名稱。
RequestDispatcher rd = getServletContext().getNamedDispatcher (“catalog”); |
RequestDispatcher接口有兩個方法,允許你在調(diào)用的servlet完成初步處理后把請求響應(yīng)分配到另外一個資源,
forward()方法:
public void forward(ServletRequest request, ServletReponse reponse) throws SwerletException,IOException
forward方法上讓你把請求轉(zhuǎn)發(fā)到另外的Servlet或者jsp或者html等資源,由這個資源接下來負(fù)責(zé)響應(yīng)。如:
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); rd. forward(request,response); |
include()方法:
public void include (ServletRequest request, ServletReponse reponse) throws SwerletException,IOException
include方法讓你的Servlet響應(yīng)中包含另外一個資源生成內(nèi)容
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); rd. include(request,response); |
結(jié)合WebWork的具體分析
WebWork是由OpenSymphony組織開發(fā)實現(xiàn)MVC模式的J2EE Web框架。在介紹完servlet規(guī)范的相關(guān)內(nèi)容后,我們看看WebWork是如何注入到一個Servlet中的,假設(shè)我們有一個上下文環(huán)境為“/WebWorkdDemo”的WEB應(yīng)用。
部署描述符
在部署描述符中,我們需要進(jìn)行如下配置:
<servlet> <servlet-name>webwork</servlet-name> <servlet-class>com.opensymphony.webwork.dispatcher.ServletDispatcher</servlet-class> </servlet> …… <servlet-mapping> <servlet-name>webwork</servlet-name> <url-pattern>*.action</url-pattern> </servlet-mapping> |
我們聲明了一個名為webwork的Servlet和*.action到這個Servlet的映射,這個Servlet就是webwork中的controller,擔(dān)任MVC框架中非常重要的控制器角色。
映射請求到Servlet
在XWork的配置文件xwork.xml中有如下片段:
<action name="demo" class=" webworkapp.DemoAction"> ??? <result name="success" type="dispatcher"> ?????? <param name="location">/demo.jsp</param> ??? </result> </action> |
這樣我們由http://localhost:8080/WebWorkDemo/demo.action這個URL向服務(wù)器發(fā)出請求時,WEB容器首先確定轉(zhuǎn)到哪一個WEB應(yīng)用程序,容器將請求URL和上下文環(huán)境進(jìn)行匹配后知道將轉(zhuǎn)到/WebWorkdDemo這個WEB應(yīng)用。
接下來容器會在/WebWorkdDemo這個應(yīng)用的部署描述符中進(jìn)行查找處理這個請求的servlet,根據(jù)后綴*.action找到名稱為webwork這個Servlet,這樣根據(jù)部署描述符,這個請求被映射到webwork中的controller組件com.opensymphony.webwork.dispatcher.ServletDispatcher來處理。這個擔(dān)任控制器組件的Servlet在他的service()方法中在根據(jù)請求的路徑解析出對應(yīng)的action來進(jìn)行處理。
通過上面的的處理,實現(xiàn)了將web請求轉(zhuǎn)到了webwork中的控制器ServletDispatcher。不止是webwork,實現(xiàn)MVC的web框架都需要進(jìn)行類似的處理來將web請求轉(zhuǎn)入到自己的controller.以便進(jìn)行進(jìn)一步的處理。
Servlet生存周期
ServletDispatcher這個Servlet的存周期可以如下:
1)????? 在服務(wù)器啟動的時候,容器首先實例化ServletDispatcher
2)??????? 實例化完成后,將調(diào)用init()方法,在init方法中執(zhí)行了以下操作:
????????? 初始化Velocity引擎
????????? 檢查是否支持配置文件重新載入功能。如果支持,每個request請求都將重新裝載xwork.xml配置文件,在開發(fā)時非常方便。
????????? 設(shè)置一些文件上傳的信息,比如:上傳臨時目錄,上傳的最大字節(jié)等。
3)????? 每次請求都調(diào)用service()方法,在service方法中執(zhí)行了以下方法
????????? 通過request請求取得action的命名空間
????????? 根據(jù)servlet請求的Path,解析出要調(diào)用該請求的Action的名字(actionName)
????????? 創(chuàng)建Action上下文(extraContext),遍歷HttpServletRequest、HttpSession、ServletContext 中的數(shù)據(jù),并將其復(fù)制到Webwork的Map實現(xiàn)中,至此之后,所有數(shù)據(jù)操作均在此Map結(jié)構(gòu)中進(jìn)行,從而將內(nèi)部結(jié)構(gòu)與Servlet API相分離。
????????? 以上述信息作為參數(shù),調(diào)用ActionProxyFactory創(chuàng)建對應(yīng)的ActionProxy實例。ActionProxyFactory 將根據(jù)Xwork 配置文件(xwork.xml)中的設(shè)定,創(chuàng)建ActionProxy實例,ActionProxy中包含了Action的配置信息(包括Action名稱,對應(yīng)實現(xiàn)類等等)。
????????? 執(zhí)行proxy的execute()方法
4)????? 容器移除Servlet 時執(zhí)行destroy(),在ServletDispatcher這個Servlet中并沒有重寫destroy方法,在移除Servlet時,將什么也不做。
請求分發(fā)
WebWork提供了多種活靈活視圖展現(xiàn)方式,例如還是我們上面在xwork.xml中的配置:
<action name="demo" class=" webworkapp.DemoAction"> ??? <result name="success" type="dispatcher"> ?????? <param name="location">/demo.jsp</param> ??? </result> </action> |
根據(jù)以上配置當(dāng)DemoAction的返回值為"success"時的處理類型為"dispatcher",當(dāng)result的type為"dispatcher"時,通過javax.servlet.RequestDispatcher的forward()或include()方法將處理結(jié)果和表現(xiàn)層融合后展現(xiàn)給用戶
我們可以看看WebWork提供的dispatcher類型Result Type的實現(xiàn)類com.opensymphony .webwork.dispatcher.ServletDispatcherResult中的代碼片斷:
? HttpServletRequest request = ServletActionContext.getRequest(); ? HttpServletResponse response = ServletActionContext.getResponse(); ? RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); ? ? if (dispatcher == null) { ??? response.sendError(404, "result '" + finalLocation + "' not found");?? ??? return; ? } ? ? if (!response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) { ??? request.setAttribute("webwork.view_uri", finalLocation); ??? request.setAttribute("webwork.request_uri", request.getRequestURI()); ??? ??? dispatcher.forward(request, response); ? } else { ??? dispatcher.include(request, response); ? } |
ServletDispatcherResult類的從ServletActionContex中得到HttpServletRequest和HttpServletResponse,然后調(diào)用request.getRequestDispatcher(finalLocation)方法得到一個RequestDispatcher實例,如果返回的是null,則輸出404頁面未找到的錯誤,否則將調(diào)用dispatcher.forward(request, response)或者dispatcher.include(request, response)進(jìn)行請求分發(fā),將處理結(jié)果和表現(xiàn)層融合后展現(xiàn)給用戶。
結(jié)束語
?????? 通過以上的介紹,我們對web框架是如何注入到servlet中有了簡單的了解,如果想更深入的研究,可以閱讀Servlet規(guī)范以及一些成熟框架的源碼。