引言
Web
框架一般是通過一個
Servlet
提供統一的請求入口,將指定的資源映射到這個
servlet,
在這個
servlet
中進行框架的初始化配置,訪問
Web
頁面中的數據,進行邏輯處理后,將結果數據與的表現層相融合并展現給用戶。
WEB
框架想要在符合
Servlet
規范的容器中運行,同樣也要符合
Servlet
規范。
將一個
WEB
框架注入到一個
servlet
中,主要涉及到
Servlet
規范中以下部分:
?????????
部署描述符
?????????
映射請求到
Servlet
?????????
Servlet
生存周期
?????????
請求分發
?Servlet
相關技術規范簡介
部署描述符
部署描述符就是位于
WEB
應用程序的
/WEB-INF
目錄下的
web.xml
的
XML
文件,是
WEB
應用程序不可分割的部分,管理著
WEB
應用程序的配置。部署描述符在應用程序開發人員,應用程序組裝人員,應用程序部署人員之間傳遞
WEB
應用程序的元素和配置信息。
在
WEB
應用程序的部署描描述符中以下類型的配置和部署信息是所有的
servlet
容器必須支持的:
?????????
ServletContext
初始化參數
?????????
Session
配置
????????
Servlet
聲明
????????
Servlet
映射
????????
應用程序生存周期監聽器
?????????
Filter
的定義和映射
?????????
MIME
類型的映射
????????
歡迎文件列表
????????
錯誤文件列表
出現在部署描述符中的安全信息可以不被支持,除非這個
Servlet
容器是
J2EE
規范實現的一部分。
所有正確的
WEB
應用程序部署描述符
(Servlet2.3
規范
)
必須包含下面的
DOCTYPE
聲明:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
|
?
下面說明在部署描述符中是如何進行
Servlet
聲明和映射的,這個
DTD
的全部內容可以在下面這個地址獲得:
http://java.sun.com/dtd/web-app_2_3.dtd
在這個
DTD
中有關
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)>
|
根據以上
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,它的實現類為com.mycorp.CatalogServlet,并且帶有一個catalog參數,參數值為Spring,所有向/catalog/*的請求都被映射到名稱為catalog的Servlet。
?
映射請求到
Servlet
接
收到一個請求后,WEB容器要確定轉到哪一個WEB應用程序。被選擇的應用程序的最長的上下文路徑必須和請求的URL開始部分匹配。URL匹配的部分是映射到Servlet的上下文路徑。
WEB
容器下一步必須按照下面的程序定位處理請求的Servlet。
用來映射到Servlet的路徑是請求對象的URL減去上下文的路徑。下面的URL路徑映射規則按順序執行,容器選擇第一個成功的匹配并且不在進行下一個匹配:
????????
容器試著對請求的路徑和Servlet的路徑進行精確匹配,如果匹配成功則選擇這個Servlet。
????????
容器會循環的去試著匹配最長的路徑前綴:把’/’當作路徑分隔符,按照路徑樹逐級遞減的完成,選擇最長匹配的Servlet。
????????
如果這個URL路徑的最后有擴展名(比如.jsp),Servlet容器會試著匹配處理這個擴展名的Servlet。
????????
如果前面的沒有與前面三條規則相匹配的Servlet,容器會試著為資源請求提供適當的資源,如果有“默認”的Servlet定義給這個應用程序,那么這個Servlet會被使用。
?
容器必須使用一個大小寫敏感的匹配方式。
在部署描述符中,用下面的語法定義映射:
????????
一個以’/’開始并且以’/*’結束的字符串用來映射路徑。
????????
一個以’*.’為前綴的字符串用來映射擴展名。
????????
一個只包含’/’的字符串指示著這個應用程序“默認”的Servlet,在這種情況下,servlet的路徑是請求的URI減去上下文路徑,并且這個路徑是null。
????????
所有其他的字符只用來精確匹配。
如果容器內置JSP容器,那么*.jsp被映射到這個容器,并允許JSP頁面在需要的時候被執行。這種映射叫做隱含映射。如果WEB應用程序中定義了*.jsp的映射,那么這個映射有比隱含映射高的優先級。
WEB
容器允許顯式的聲明隱含映射以獲得優先級,例如,*.shtml的隱含映射可以在服務器上被映射為包含功能。
映射實例:
path pattern
|
servlet
|
/foo/bar/*
|
servlet1
|
/baz/*
|
servlet2
|
/catalog
|
servlet3
|
*.bop
|
servlet4
|
下面是實際請求映射的結果
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
必須實現或者間接實現這個借口,我們通常可以通過繼承
javax.servlet.GenericServlet
或者
javax.servlet.http.HttpServlet.
類來實現這個接口。
這個接口中定義了下面
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
時執行,
Servlet
容器在實例化后只調用一次
init
方法,
init
方法必須在
servlet
接收到任何請求之前完成。
這個方法通常用來進行一些資源的管理和初始化,如從配置文件讀取配置數據,讀取初始化參數,初始化緩沖遲等一次性的操作。
getServletConfig()
方法
GetServletConfig
方法返回一個
ServletConfig
對象,該對象用來返回這個
Servlet
的初始化信息和啟動參數。返回的是傳遞到
init
方法
ServletConfig
。
Service()
方法
Service
方法是應用程序邏輯的進入點,是
servlet
方法的核心,
WEB
容器調用這個方法來響應進入的請求,只有
servlet
成功被
init()
方法初始化后,
Service
方法才會被調用。
getServletInfo()
方法
這個方法返回一個字符串對象,提供有關
servlet
的信息,如作者、版本等。
destroy()
方法
destroy
方法在容器移除
Servlet
時執行,同樣只執行一次。這個方法會在所有的線程的
service()
方法執行完成或者超時后執行,調用這個方法后,容器不會再調用這個
servlet
的方法,也就是說容器不再把請求發送給這個
Servlet
。
?????
這個方法給
servlet
釋放占用的資源的機會,通常用來執行一些清理任務。
?
這個接口定義了初始化一個
servlet,
服務請求和從容器中移除
servlet
的方法。他們按照下面的順序執行:
1.????????
servlet
被實例化后,用
init
方法進行初始化
2.????????
客戶端的任何請求都調用
service
方法
3.????????
servlet
被移除服務,調用
destroy
方法銷毀
servlet
的生存周期如下圖:

?
請求分發
請求分發可以讓一個Servlet把請求分配到另外一個資源,RequestDispatcher接口提供了實現他的機制。可以通過下面兩種方式從ServletContext中獲得一個實現了RequestDispatcher接口的對象:
? getRequestDispatcher
? getNamedDispatcher
getRequestDispatcher方法接受一個指向目標資源的URL路徑
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); ? |
?
getNamedDispatcher方法接受一個Servlet名稱參數,這個名稱是在部署描述符中<servlet-name>元素指定的那個名稱。
RequestDispatcher rd = getServletContext().getNamedDispatcher (“catalog”); ? |
?
RequestDispatcher接口有兩個方法,允許你在調用的servlet完成初步處理后把請求響應分配到另外一個資源,
forward()方法:
public void forward(ServletRequest request, ServletReponse reponse) throws SwerletException,IOException
forward方法上讓你把請求轉發到另外的Servlet或者jsp或者html等資源,由這個資源接下來負責響應。如:
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); rd. forward(request,response); ? |
include()方法:
public void include (ServletRequest request, ServletReponse reponse) throws SwerletException,IOException
include方法讓你的Servlet響應中包含另外一個資源生成內容
RequestDispatcher rd = getServletContext().getRequestDispatcher(“/catalog”); rd. include(request,response); ? |
?
結合WebWork的具體分析
WebWork是由OpenSymphony組織開發實現MVC模式的J2EE Web框架。在介紹完servlet規范的相關內容后,我們看看WebWork是如何注入到一個Servlet中的,假設我們有一個上下文環境為“/WebWorkdDemo”的WEB應用。
部署描述符
在部署描述符中,我們需要進行如下配置:
<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,擔任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向服務器發出請求時,WEB容器首先確定轉到哪一個WEB應用程序,容器將請求URL和上下文環境進行匹配后知道將轉到/WebWorkdDemo這個WEB應用。
接下來容器會在/WebWorkdDemo這個應用的部署描述符中進行查找處理這個請求的servlet,根據后綴*.action找到名稱為webwork這個Servlet,這樣根據部署描述符,這個請求被映射到webwork中的controller組件com.opensymphony.webwork.dispatcher.ServletDispatcher來處理。這個擔任控制器組件的Servlet在他的service()方法中在根據請求的路徑解析出對應的action來進行處理。
通過上面的的處理,實現了將web請求轉到了webwork中的控制器ServletDispatcher。不止是webwork,實現MVC的web框架都需要進行類似的處理來將web請求轉入到自己的controller.以便進行進一步的處理。
Servlet生存周期
ServletDispatcher這個Servlet的存周期可以如下:
1)????? 在服務器啟動的時候,容器首先實例化ServletDispatcher
2)??????? 實例化完成后,將調用init()方法,在init方法中執行了以下操作:
????????? 初始化Velocity引擎
????????? 檢查是否支持配置文件重新載入功能。如果支持,每個request請求都將重新裝載xwork.xml配置文件,在開發時非常方便。
????????? 設置一些文件上傳的信息,比如:上傳臨時目錄,上傳的最大字節等。
3)????? 每次請求都調用service()方法,在service方法中執行了以下方法
????????? 通過request請求取得action的命名空間
????????? 根據servlet請求的Path,解析出要調用該請求的Action的名字(actionName)
????????? 創建Action上下文(extraContext),遍歷HttpServletRequest、HttpSession、ServletContext 中的數據,并將其復制到Webwork的Map實現中,至此之后,所有數據操作均在此Map結構中進行,從而將內部結構與Servlet API相分離。
????????? 以上述信息作為參數,調用ActionProxyFactory創建對應的ActionProxy實例。ActionProxyFactory 將根據Xwork 配置文件(xwork.xml)中的設定,創建ActionProxy實例,ActionProxy中包含了Action的配置信息(包括Action名稱,對應實現類等等)。
????????? 執行proxy的execute()方法
4)????? 容器移除Servlet 時執行destroy(),在ServletDispatcher這個Servlet中并沒有重寫destroy方法,在移除Servlet時,將什么也不做。
請求分發
WebWork提供了多種活靈活視圖展現方式,例如還是我們上面在xwork.xml中的配置:
<action name="demo" class=" webworkapp.DemoAction"> ??? <result name="success" type="dispatcher"> ?????? <param name="location">/demo.jsp</param> ??? </result> </action> |
根據以上配置當DemoAction的返回值為"success"時的處理類型為"dispatcher",當result的type為"dispatcher"時,通過javax.servlet.RequestDispatcher的forward()或include()方法將處理結果和表現層融合后展現給用戶
我們可以看看WebWork提供的dispatcher類型Result Type的實現類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,然后調用request.getRequestDispatcher(finalLocation)方法得到一個RequestDispatcher實例,如果返回的是null,則輸出404頁面未找到的錯誤,否則將調用dispatcher.forward(request, response)或者dispatcher.include(request, response)進行請求分發,將處理結果和表現層融合后展現給用戶。
?
結束語
?????? 通過以上的介紹,我們對web框架是如何注入到servlet中有了簡單的了解,如果想更深入的研究,可以閱讀Servlet規范以及一些成熟框架的源碼。