深入剖析tomcat之servlet容器
其實我們開發中經常用tomcat應用伺服器,tomcat就一個servlet容器,能夠執行基於serlvlet的應用程式並響應相應的http請求,開發時間長了,還是想想具體知道它是怎麼執行的,尤其是servlet容器的機理,所以有幸拜讀了外國人的《深入剖析tomcat》,感覺挺不錯的,可以在此點選免費下載電子書,建議大家有時間讀讀,在讀的過程中邊讀邊翻閱著tomcat的原始碼,更有助於你理解它的各個機制,此處有tomcat 7的原始碼,點選可免費下載。
本人目前時間和組織語言能力及功力有限,心有餘而力不足,網上看到有好心人(randyjiawenjie
主要是《深入剖析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容器將要呼叫的任務集合,定義如下:
- publicinterface Pipeline {
- public Valve getBasic();
- publicvoid setBasic(Valve valve);
- publicvoid addValve(Valve valve);
- public Valve[] getValves();
- publicvoid invoke(Request request, Response response) throws IOException, ServletException;
- publicvoid removeValve(Valve valve);
- }
valve表示一個具體的執行任務,定義如下:
- publicinterface Valve {
- public String getInfo();
- publicvoid invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
- }
valveContext按照字面意思就是閥門的上下文,用於遍歷valve
- publicinterface ValveContex{
- public String getInfo();
- publicvoid invokeNext(Request request, Response response) throws IOException, ServletException;
- }
valveContext的invokeNext()方法的實現:
- publicfinalvoid invokeNext(Request request, Response response) throws IOException, ServletException {
- int subscript = stage; stage = stage + 1;
- if (subscript < valves.length)
- { valves[subscript].invoke(request, response, this); }
- elseif ((subscript == valves.length) && (basic != null))
- {
- basic.invoke(request, response, this);
- }
- else
- {
- thrownew ServletException (sm.getString("standardPipeline.noValve"));
- }
- }
一個閥門的invoke方法可以如下實現:
- publicvoid 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()方法實現為:
- publicvoid 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實現示例如下:
- publicvoid 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定義如下:
- publicinterface Mapper {
- public Container getContainer();
- publicvoid setContainer(Container container);
- public String getProtocol();
- publicvoid setProtocol(String protocol);
- public Container map(Request request, boolean update);
- }
map方法返回一個子容器(wrapper)負責來處理請求。map方法有兩個引數,一個request物件和一個boolean值。實現將忽略第二個引數。這個方法是從請求物件中獲取context路徑和使用context的findServletMappi