How Tomcat Works 5

上一節簡單介紹了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處理的過程大概就如上面所講。


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