上一節簡單介紹了tomcat中的流水線,當connector收到一條消息的時候,將socket交給processor來處理,processor構造出來request和response對象並解析http請求,然後processor調用container的的invoke方法來處理這兩個對象。invoke方法是父類ContainerBase中的方法,主要是調用該Container對應的Pipeline處理請求。Tomcat中StandardEngine對應的pipeline是StandardPipeline,其中的basicValve是StandardEngineValve,每個basicValve都是該容器中最後被調用的valve。它其中的一個任務便是調用自己下層組件的invoke方法,繼續流水線的處理,直到最後的的wrappervalve。值得提一點的是一個servlet對應一個wrapper。流程圖如下:
本節重點介紹的是tomcat處理一個請求的大體過程。
在tomcat的外層web.xlm會配置一個默認的serverlet——org.apache.catalina.servlets.DefaultServlet當根據mapping未匹配到需要處理的servlet的時候,會使用該servlet處理請求,DefaultServlet在tomcat啓動的時候便會被加載。
假設我們訪問的地址爲http://localhost:8080/myapp/primi myapp是我自己寫的一個app,非常簡單的結構如下:
在WEB-INF下只有一個web.xml,配置了servlet的映射關係
<servlet>
<servlet-name>PrimitiveServlet</servlet-name>
<servlet-class>PrimitiveServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
</servlet>
<!-- Define the Manager Servlet Mapping -->
<servlet-mapping>
<servlet-name>PrimitiveServlet</servlet-name>
<url-pattern>/primi</url-pattern>
</servlet-mapping>
那麼下面就來看看tomcat是如何一步步找到這個servlet並處理我們的請求的。
connector將收到的請求交給Engine來處理,一個在Engine的標準valve中會調用下面的方法來匹配到對應的host
// Select the Host to be used for this Request
StandardEngine engine = (StandardEngine) getContainer();
Host host = (Host) engine.map(request, true);
if (host == null) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getRequest().getServerName()));
return;
}
// Ask this Host to process this request
host.invoke(request, response);
可以看到通過engine的map方法找到對應的host,其中在tomcat4版本中匹配url的過程是通過Mapper類完成的,這裏會匹配到標識爲localhost的Host容器,也就是所有對localhost的訪問都會由localhost容器處理,獲取到對應的Host後調用它的invoke方法,接下來的處理過程類似上面。
// Select the Context to be used for this Request
StandardHost host = (StandardHost) getContainer();
Context context = (Context) host.map(request, true);
if (context == null) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
Context對應的就是webapps目錄下一個個的應用程序,由Host匹配合適Context的方法在StandardHostMapper類中:
public Container map(Request request, boolean update) {
// Has this request already been mapped?
if (update && (request.getContext() != null))
return (request.getContext());
// Perform mapping on our request URI
String uri = ((HttpRequest) request).getDecodedRequestURI();
Context context = host.map(uri);
// Update the request (if requested) and return the selected Context
if (update) {
request.setContext(context);
if (context != null)
((HttpRequest) request).setContextPath(context.getPath());
else
((HttpRequest) request).setContextPath(null);
}
return (context);
}
獲取到uri,該例子中爲/myapp/primi。由於我們在server.xml中配置了localhost下有一個名字爲myapp的Context,所以按照從後往前按照反斜槓/截取的過程,最終會匹配到mayapp這個context,同樣類似上面的處理過程,context的invoke方法會被調用。也就是請求和響應消息被傳遞給名字爲myapp的context來處理。
StandardContextValve中隊消息的處理代碼如下:
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);
上面的主要邏輯是在一個應用上下文中匹配出對該消息處理的servlet,這裏是一個Wrapper。由於我們設置了servletmapping,因此會匹配到StandardWrapper[PrimitiveServlet]這個wrapper。最後會由StandardWrapperValve處理該消息,主要邏輯如下:
servlet = wrapper.allocate();
上面是非常重要的一個步驟,就是獲取一個對應的Servlet實例,allocate的主要邏輯如下:
if (!singleThreadModel) {
// Load and initialize our instance if necessary
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
instance = loadServlet();
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
}
}
}
if (!singleThreadModel) {
if (debug >= 2)
log(" Returning non-STM instance");
countAllocated++;
return (instance);
}
}
該方法中會判斷一個Servlet是否實現了單線程的模式,如果不是單線程的模式每次都會返回相同的實例,也就是隻有一個實例存在。生成servlet的方法由loadServlet使用類加載器加載對應的Servelet,加載過程我們前面幾節已經涉及到了,這裏不再贅述(針對servlet的init和service等方法的調用是在loadServlet方法中完成的)。如果是單線程模式會將特定數量的實例放到對象池中,每次從池中獲取可用的對象,如果池中沒有可用的實例,此時會阻塞,可以看出單線程模式會有性能問題。
到此針對一個請求尋找到合適的servlet處理的過程大概就如上面所講。