tomcat源碼分析-http請求在Container中的執行路線

    在CoyoteAdapter的service方法中,主要乾了2件事:java

    1. org.apache.coyote.Request -> org.apache.catalina.connector.Request extends HttpServletRequestweb

        org.apache.coyote.Response -> org.apache.catalina.connector. Response extends HttpServletResponseapache

        Context和Wrapper定位編程

    2. 將請求交給StandardEngineValue處理設計模式

 

public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res) {
    //
    postParseSuccess = postParseRequest(req, request, res, response);
    //
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
    //
}

postParseRequest方法的代碼片斷tomcat

  

connector.getMapper().map(serverName, decodedURI, version,
                                      request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);

request經過URI的信息找到屬於本身的Context和Wrapper。而這個Mapper保存了全部的容器信息,不記得的同窗能夠回到Connector的startInternal方法中,最有一行代碼是mapperListener.start()。在MapperListener的start()方法中,數據結構

  

public void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);
    findDefaultHost();

    Engine engine = (Engine) connector.getService().getContainer();
    addListeners(engine);

    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            registerHost(host);
        }
    }
}

在容器初始化和變化時都會觸發監聽事件,從而將全部容器信息保存在Mapper中。之因此叫Mapper,由於它的主要做用就是定位Wrapper,而咱們在web.xml裏也配了filter/servlet-mapping。app

 

    另外,由上面的代碼可知,在隨後的請求路線中,Engine可有Connector獲取,Context和Wrapper可直接由Request獲取,Host也可由Request獲取。分佈式

public Host getHost() { return ((Host) mappingData.host); }

 

    上面的代碼中還涉及到了兩個很重要的概念--Pipeline和Value,咱們不妨先一睹Container的調用鏈和時序圖。ide




 
     對於每一個引入的http請求,鏈接器都會調用與其關聯的servlet容器所綁定的一系列閥門(Value)的invoke方法,基礎閥門(StandardXxValue)都在尾端,而後會逐步調用子容器的閥門。爲何必需要有一個Host容器呢?

    在tomcat的實際部署中,若一個Context實例使用ContextConfig對象進行設置,就必須使用一個Host對象,緣由以下:

    使用ContextConfig對象須要知道應用程序web.xml文件的位置,在其webConfig()方法中會解析web.xml文件

 

// Parse context level web.xml
        InputSource contextWebXml = getContextWebXmlSource();
        parseWebXml(contextWebXml, webXml, false);

 

在getContextWebXmlSource方法裏

// servletContext即core包下的ApplicationContext
url = servletContext.getResource(Constants.ApplicationWebXml);

 

在getResource方法裏

String hostName = context.getParent().getName();

 所以,除非你本身實現一個ContextConfig類,不然,你必須使用一個Host容器。

 

    管道(Pipeline)包含該servlet容器將要調用的任務。一個閥(Value)表示一個具體的執行任務。在servlet容器的管道中,有一個基礎閥,可是,能夠添加任意數量的閥。閥的數量指的是額外添加的閥數量,即不包括基礎閥。有意思的是,能夠經過server.xml來動態添加閥。

    管道和閥的工做機制相似於servlet編程中的過濾器鏈和過濾器,tomcat的設計者採用的是鏈表數據結構來實現的鏈條機制,引入了一個類叫ValueContext。值得注意的是,基礎閥老是最後執行。

    請求最終會被引導到StandardWrapper,本人也是首先從Wrapper這一層來入手Container的,直接看StandardWrapperValue的invoke方法

 

@Override
public final void invoke(Request request, Response response) {
    //

    requestCount++;
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

    //

    // Allocate a servlet instance to process this request
    try {
       if (!unavailable) {
          servlet = wrapper.allocate();
       }
    } catch (Exception e) {}

    //

    // Create the filter chain for this request
    ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
    ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);

    //

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    //
    filterChain.doFilter(request.getRequest(), response.getResponse());
    //

    // Release the filter chain (if any) for this request
    if (filterChain != null) filterChain.release();

    // Deallocate the allocated servlet instance
    if (servlet != null) wrapper.deallocate(servlet);

// If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                wrapper.unload();
            }
        } catch (Throwable e) { }

    //
}

 

上面代碼中最重要的三處邏輯就是servlet實例的獲取與卸載和filter鏈調用。咱們先看卸載servlet實例的代碼

@Override
    public void deallocate(Servlet servlet) throws ServletException {

        // If not SingleThreadModel, no action is required
        if (!singleThreadModel) {
            countAllocated.decrementAndGet();
            return;
        }

        // Unlock and free this instance
        synchronized (instancePool) {
            countAllocated.decrementAndGet();
            instancePool.push(servlet);
            instancePool.notify();
        }

    }

 

咱們不考慮SingleThreadModel模型,由於較新版本的tomcat已經不用這種模型了(只有很老的版本才用),顯然,經過上面的代碼能夠知道,基本上什麼都不用作,而Single Thread Model經常使用的是池化模型(maxInstances=20)。下面給出加載servlet實例的代碼

@Override
public Servlet allocate() throws ServletException {
      boolean newInstance = false;

        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {
            // Load and initialize our instance if necessary
            if (instance == null || !instanceInitialized) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            if (log.isDebugEnabled()) {
                                log.debug("Allocating non-STM instance");
                            }

                            // Note: We don't know if the Servlet implements
                            // SingleThreadModel until we have loaded it.
                            instance = loadServlet();
                            newInstance = true;
                            if (!singleThreadModel) {
                                // For non-STM, increment here to prevent a race
                                // condition with unload. Bug 43683, test case
                                // #3
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ExceptionUtils.handleThrowable(e);
                            throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                    if (!instanceInitialized) {
                        initServlet(instance);
                    }
                }
            }

            if (singleThreadModel) {
                if (newInstance) {
                    // Have to do this outside of the sync above to prevent a
                    // possible deadlock
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("  Returning non-STM instance");
                }
                // For new instances, count will have been incremented at the
                // time of creation
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return instance;
            }
        }

        synchronized (instancePool) {
            while (countAllocated.get() >= nInstances) {
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            if (log.isTraceEnabled()) {
                log.trace("  Returning allocated STM instance");
            }
            countAllocated.incrementAndGet();
            return instancePool.pop();
        }
}

 咱們看到了:If not SingleThreadedModel, return the same instance every time

最後,咱們來看看filterChain的執行,

@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

    internalDoFilter(request,response);
}

private void internalDoFilter(ServletRequest request, 
                                  ServletResponse response)
        throws IOException, ServletException {

    // Call the next filter if there is one
    if (pos < n) {
        filter.doFilter(request, response, this);
    }

     // We fell off the end of the chain -- call the servlet instance
     servlet.service(request, response);
}

 

顯然,在調用web.xml裏配的某個servlet時,都會先依次調用在web.xml裏配的filter,這可謂是責任鏈設計模式的一種經典實現,方法的最後會調用servlet.service(request, response)。

 

一個Servlet到底有多少個實例呢,咱們來看看官方的說明,在Servlet規範中,對於Servlet單例與多例定義以下:

 

「Deployment Descriptor」, controls how the servlet container provides instances of the servlet.For a servlet not hosted in a distributed environment (the default), the servlet container must use only one instance per servlet declaration. However, for a servlet implementing the SingleThreadModel interface, the servlet container may instantiate multiple instances to handle a heavy request load and serialize requests to a particular instance.

 

上面規範提到,

若是一個Servlet沒有被部署在分佈式的環境中,通常web.xml中聲明的一個Servlet只對應一個實例。

而若是一個Servlet實現了SingleThreadModel接口,就會被初始化多個實例,默認20個

補充如下,一個Servlet在web.xml聲明兩次,會產生兩個實例。

   

    好了,如今你能夠把前文中Connector執行過程和本文的Container執行過程結合起來了。我始終相信,深刻一點,你會更快樂。

相關文章
相關標籤/搜索