阿里大神----教你如何深入剖析tomcat之servlet容器
其實我們開發中經常用tomcat應用伺服器,tomcat就一個servlet容器,能夠執行基於serlvlet的應用程式並響應相應的http請求,開發時間長了,還是想想具體知道它是怎麼執行的,尤其是servlet容器的機理,所以有幸拜讀了外國人的《深入剖析tomcat》,感覺挺不錯的,可以在此點選免費下載電子書,建議大家有時間讀讀,在讀的過程中邊讀邊翻閱著tomcat的原始碼,更有助於你理解它的各個機制,此處有tomcat 7的原始碼,點選可免費下載。
本人目前時間和組織語言能力及功力有限,心有餘而力不足,網上看到有好心人(<span style="font-family:Microsoft YaHei">randyjiawenjie</span>)做過記錄,大概就是把書中重要的東西摘錄的了一下,本人就就特意摘錄以下了。再就是為了更有助理解,大家可以參考Tomcat 容器與servlet的互動原理,簡單介紹了serlvet的原理和生命週期,還有學習tomcat原始碼(2) 實現servlet容器功能,這篇文章主要還是電子書的程式碼及解釋,昇華的文章Tomcat 系統架構與設計模式,第 2 部分: 設計模式分析。
主要是《深入剖析tomcat》的第五章和第十一章。個人覺得如下3點是關鍵:
1. pipeline相關概念及其執行valve的順序;
2. standardwrapper的接受http請求時候的呼叫序列;
3. standardwrapper基礎閥載入servlet的過程(涉及到STM);
順便問一句,應該每一個servlet程式設計師都知道filter。但是你知道Filter在tomcat的哪一個地方出現的嗎?答案是standardwrapper基礎閥會建立Filter鏈,並呼叫doFilter()方法
對Java感興趣的同學可以加入到我們的學習 交流群:450936584 每天都會分享最新的視訊和資料 可以免費領取學習視訊和資料
servlet容器的作用
管理servlet資源,併為web客戶端填充response物件。
不同級別的容器
Engine:表示整個Catalina的servlet引擎、
Host:表示一個擁有數個上下文的虛擬主機
Context:表示一個Web應用,一個context包含一個或多個wrapper
Wrapper:表示一個獨立的servlet
容器都實現了Container介面,繼承了ContainerBase抽象類。
管道任務
3個概念:pipeline、valve、valveContext
pipeline包含servlet容器將要呼叫的任務集合,定義如下:
[java]
-
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表示一個具體的執行任務,定義如下:
[java]view plaincopy
-
publicinterface Valve {
-
public String getInfo();
-
publicvoid invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
-
}
valveContext按照字面意思就是閥門的上下文,用於遍歷valve
[java]view plaincopy
-
publicinterface ValveContex{
-
public String getInfo();
-
publicvoid invokeNext(Request request, Response response) throws IOException, ServletException;
-
}
valveContext的invokeNext()方法的實現:
[java]view plaincopy
-
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方法可以如下實現:
[java]view plaincopy
-
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()方法實現如下:
[java]view plaincopy
-
valveContext.invokeNext(request, response);
-
System.out.println(“valve1 invoke!”);
如果注意到了invokeNext()的實現,這層呼叫類似與下面的圖:
類似與遞迴呼叫,輸出為
[java]view plaincopy
-
“valve3 invoke!”
-
“valve2 invoke!”
-
“valve1 invoke!”
順序恰好於新增的順序相反。所以這個也可以說明,為什麼基礎閥看起來是放在最後的,但確實要做裝載servlet這樣的操作。其實,放在最後的閥門總是第一個被呼叫。書中的依次新增HeaderLoggerValve、ClientIPLoggerValve依次新增,那麼呼叫順序為ClientIPLoggerValve、HeaderLoggerValve。注意到書中示例程式的執行結果和新增閥門的順序。不過話說回來,如果valve的invoke()方法實現為:
[java]view plaincopy
-
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實現示例如下:
[java]view plaincopy
-
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定義如下:
[java]view plaincopy
-
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的findServletMapping方法獲取相關的路徑名。如果一個路徑名被找到,它使用context的findChild方法獲取一個Wrapper的例項。一個mapper的實現:
[java]view plaincopy
-
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);
[java]view plaincopy
-
}
容器包含一條管道,容器的invoke方法會呼叫pipeline的invoke方法。
1. pipeline的invoke方法會呼叫新增到容器中的閥門的invoke方法,然後呼叫基本閥門的invoke方法。
2.在一個wrapper中,基礎閥負責載入相關的servlet類並對請求作出相應。
3. 在一個包含子容器的Context中,基礎閥使用mapper來查詢負責處理請求的子容器。如果一個子容器被找到,子容器的invoke方法會被呼叫,然後返回步驟1。
Context的一個基礎閥示例如下:注意最後一句話:wrapper.invoke(request, response);
[java]view plaincopy
-
publicvoid 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例項相關聯