《深入剖析tomcat》讀書筆記3--servlet容器

主要是《深入剖析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
方法調用序列Sequence of Methods Invocation
對於每一個連接,連接器都會調用關聯容器的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方法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章