主要是《深入剖析tomcat》的第五章和第十一章。個人覺得如下3點是關鍵:
1. pipeline相關概念及其執行valve的順序;
2. standardwrapper的接受http請求時候的調用序列;
3. standardwrapper基礎閥加載servlet的過程(涉及到STM);
順便問一句,應該每一個servlet程序員都知道filter。但是你知道Filter在tomcat的哪一個地方出現的嗎?答案是standardwrapper基礎閥會創建Filter鏈,並調用doFilter()方法
servlet容器的作用
管理servlet資源,併爲web客戶端填充response對象。
不同級別的容器
Engine:表示整個Catalina的servlet引擎、
Host:表示一個擁有數個上下文的虛擬主機
Context:表示一個Web應用,一個context包含一個或多個wrapper
Wrapper:表示一個獨立的servlet
容器都實現了Container接口,繼承了ContainerBase抽象類。
管道任務
3個概念:pipeline、valve、valveContext
pipeline包含servlet容器將要調用的任務集合,定義如下:
public interface Pipeline {
public Valve getBasic();
public void setBasic(Valve valve);
public void addValve(Valve valve);
public Valve[] getValves();
public void invoke(Request request, Response response) throws IOException, ServletException;
public void removeValve(Valve valve);
}
valve表示一個具體的執行任務,定義如下:
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
}
valveContext按照字面意思就是閥門的上下文,用於遍歷valve
public interface ValveContex{
public String getInfo();
public void invokeNext(Request request, Response response) throws IOException, ServletException;
}
valveContext的invokeNext()方法的實現:
public final void invokeNext(Request request, Response response) throws IOException, ServletException {
int subscript = stage; stage = stage + 1;
if (subscript < valves.length)
{ valves[subscript].invoke(request, response, this); }
else if ((subscript == valves.length) && (basic != null))
{
basic.invoke(request, response, this);
}
else
{
throw new ServletException (sm.getString("standardPipeline.noValve"));
}
}
一個閥門的invoke方法可以如下實現:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
//Pass the request and response on to the next valve in our pipeline
valveContext.invokeNext(request, response);
// now perform what this valve is supposed to do ...
}
如果pipeline由valve1、valve2、valve3組成,調用valve1. Invoke()會發生什麼?執行順序是什麼樣的?假設valveN(N=1,2,3)的invoke()方法實現如下:
valveContext.invokeNext(request, response);
System.out.println(“valve1 invoke!”);
如果注意到了invokeNext()的實現,這層調用類似與下面的圖:
類似與遞歸調用,輸出爲
“valve3 invoke!”
“valve2 invoke!”
“valve1 invoke!”
順序恰好於添加的順序相反。所以這個也可以說明,爲什麼基礎閥看起來是放在最後的,但確實要做裝載servlet這樣的操作。其實,放在最後的閥門總是第一個被調用。書中的依次添加HeaderLoggerValve、ClientIPLoggerValve依次添加,那麼調用順序爲ClientIPLoggerValve、HeaderLoggerValve。注意到書中示例程序的運行結果和添加閥門的順序。不過話說回來,如果valve的invoke()方法實現爲:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
// now perform what this valve is supposed to do ...
valveContext.invokeNext(request, response); //Pass the request and response on to the next valve in our pipeline
}
那麼閥門調用的順序就會和閥門添加的順序一致(個人感覺這樣好理解)
Wrapper接口
Wrapper表示一個獨立servlet定義的容器,wrapper繼承了Container接口,並且添加了幾個方法。包裝器的實現類負責管理其下層servlet的生命週期。
包裝器接口中重要方法有allocate和load方法。allocate方法負責定位該包裝器表示的servlet的實例。Allocate方法必須考慮一個servlet,是否實現了avax.servlet.SingleThreadModel接口。
Wrapper調用序列:
(1) 連接器調用Wrapper的invoke()方法;
(2) Wrapper調用其pipeline的invoke()方法;
(3) Pipeline調用valveContext.invokeNext()方法;
基礎閥的invoke實現示例如下:
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
SimpleWrapper wrapper = (SimpleWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres;
// Allocate a servlet instance to process this request
try {
servlet = wrapper.allocate();
if (hres!=null && hreq!=null)
{
servlet.service(hreq, hres);
}
else
{
servlet.service(sreq, sres);
}
}
catch (ServletException e) {
}
}
Context應用程序
Context包含多個wrapper,至於用哪一個wrapper,是由映射器mapper決定的。Mapper定義如下:
public interface Mapper {
public Container getContainer();
public void setContainer(Container container);
public String getProtocol();
public void setProtocol(String protocol);
public Container map(Request request, boolean update);
}
map方法返回一個子容器(wrapper)負責來處理請求。map方法有兩個參數,一個request對象和一個boolean值。實現將忽略第二個參數。這個方法是從請求對象中獲取context路徑和使用context的findServletMapping方法獲取相關的路徑名。如果一個路徑名被找到,它使用context的findChild方法獲取一個Wrapper的實例。一個mapper的實現:
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from
// the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
String name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
return (wrapper);
}
容器包含一條管道,容器的invoke方法會調用pipeline的invoke方法。
1. pipeline的invoke方法會調用添加到容器中的閥門的invoke方法,然後調用基本閥門的invoke方法。
2.在一個wrapper中,基礎閥負責加載相關的servlet類並對請求作出相應。
3. 在一個包含子容器的Context中,基礎閥使用mapper來查找負責處理請求的子容器。如果一個子容器被找到,子容器的invoke方法會被調用,然後返回步驟1。
Context的一個基礎閥示例如下:注意最後一句話:wrapper.invoke(request, response);
public void invoke(Request request, Response response,ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return;
}
// Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length()).toUpperCase();
Context context = (Context) getContainer();
// Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
}catch (IllegalArgumentException e) {
badRequest(requestURI, (HttpServletResponse)
response.getResponse());
return;
}
if (wrapper == null) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
}
// Ask this Wrapper to process this Request
response.setContext(context);
wrapper.invoke(request, response);
}
standardwrapper
對於每一個連接,連接器都會調用關聯容器的invoke方法。接下來容器調用它的所有子容器的invoke方法。如果一個連接器跟一個StadardContext實例相關聯,那麼連接器會調用StandardContext實例的invoke方法,該方法會調用所有它的子容器的invoke方法。
1. 連接器創建request和response對象;
2.連接器調用StandardContext的invoke方法;
3.StandardContext的invoke方法必須調用該管道對象的invoke方法。StandardContext管道對象的基礎閥是StandardContextValve類的實例。因此,StandardContext管道對象會調用StandardContextValve的invoke方法。
4.StandardContextValve的invoke方法獲取相應的wrapper,處理http請求,調用wrapper的invoke方法
5.StandardWrapper是wrapper的標準實現,StandardWrapper對象的invoke方法調用pipeline的invoke方法。
6,StandardWrapper流水線的基本閥門時StandardWrapperValve。因此StandardWrapperValve的invoke方法會被調用。StandardWrapperValve的invoke方法會調用包裝器的allocate方法獲得一個servlet的實例。
7,當一個servlet需要被加載的時候,方法allocate調用方法load來加載一個servlet
8,方法load會調用servlet的init方法
我們主要關注的是一個servlet被調用的時候發生的細節。因此我們需要看StandardWrapper和StandarWrapperValve類
javax.servlet.SingleThreadModel
一個servlet可以實現javax.servlet.SingleThreadModel接口,實現此接口的一個servlet通俗稱爲SingleThreadModel(STM)的程序組件。
根據Servlet規範,實現此接口的目的是保證servlet一次只能有一個請求。
StandardWrapper實現
一個StandardWrapper對象的主要職責是:加載它表示的servlet,並實例化。該StandardWrapper不會調用servlet的service方法這個任務留給StandardWrapperValve對象,在StandardWrapper實例的基本閥門管道。StandardWrapperValve對象通過調用StandardWrapper的allocate方法獲得Servlet實例。在獲得Servlet實例之後的StandardWrapperValve調用servlet的service方法。
在servlet第一次被請求的時候,StandardWrapper加載servlet類。它是動態的加載servlet,所以需要知道servlet類的完全限定名稱。通過StandardWrapper類的setServletClass方法將servlet的類名傳遞給StandardWrapper。必須考慮一個servlet是否實現了SingleThreadModel接口。 如果一個servlet沒有實現SingleThreadModel接口,StandardWrapper加載該servlet一次,對於以後的請求返回相同的實例即可。
對於一個STM servlet,情況就有所不同了。StandardWrapper必須保證不能同時有兩個線程提交STM servlet的service方法。
Servlet instance = <get an instance of the servlet>;
if ((servlet implementing SingleThreadModel>) {
synchronized (instance) {
instance.service(request, response);
}
}else{
instance.service(request, response);
}
Allocating the Servlet
StandardWrapperValve的invoke方法調用了包裝器的allocate方法來獲得一個請求servlet的實例,因此StandardWrapper類必須實現該接口
public javax.servlet.Servlet allocate() throws ServletException;
由於要支持STM servlet,這使得該方法更復雜了一點。實際上,該方法有兩部分組成,一部分負責非STM servlet的工作,另一部分負責STM servlet。
第一部分的結構如下
if (!singleThreadModel) {
// returns a non-STM servlet instance
}
布爾變量singleThreadModel負責標誌一個servlet是否是STM servlet。
它的初始值是false,loadServlet方法會檢測加載的servlet是否是STM的,如果是則將它的值該爲true
第二部分處理singleThreadModel爲true的情況
synchronized (instancepool) {
// returns an instance of the servlet from the pool
}
對於非STM servlet,StandardWrapper定義一個java.servlet.Servlet類型的實例
private Servlet instance = null;
方法allocate檢查該實例是否爲null,如果是調用loadServlet方法來加載servlet。然後增加contAllocated整型並返回該實例。
if (!singleThreadModel) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
}catch (ServletException e) {
throw e;
}
}
}
}
if (!singleThreadModel) {
countAllocated++;
return (instance);
}
StandardWrapperValve
StandardWrapperValve是StandardWrapper實例上的基本閥,主要完成2個操作:
1,執行與該servlet的所有過濾器;(看到了嗎,filter是在StandardWrapper的基礎閥出現的)
2,調用servlet實例的的service()方法
要實現這些內容,下面是StandardWrapperValve在他的invoke方法要實現的
. 調用StandardWrapper的allocate的方法來獲得一個servlet實例
·調用它的private createFilterChain方法獲得過濾鏈
· 調用過濾器鏈的doFilter方法。包括調用servlet的service方法
· 釋放過濾器鏈
· 調用包裝器的deallocate方法
· 如果Servlet無法使用了,調用Wrapper的unload方法