tomcat原始碼分析(第四篇 tomcat請求處理原理解析--Container原始碼分析)

emoo發表於2018-06-27

Container容器是所用servlet容器的父介面,也就是說作為一個servlet容器,首先必須要實現Container介面,每個tomcat伺服器只能有唯一的根Container,Connector元件通過setContainer方法將Container容器和Connector關聯起來。共有四種型別Container容器,分別對應不同概念的層次,每一層之間是父子的關係。

1、Engine:整個Catalina servlet引擎,標準實現為StandardEngine。

2、Host:表示包含一個或多個Context容器的虛擬主機,標準實現為StandardHost。

3、Context:表示一個web應用程式,一個Context可以有多個Wrapper,標準實現為StandardContext。

4、Wrapper:包裝一個獨立的Servlet容器,標準實現為StandardWrapper。

在第二節的分析中我們知道,server.xml檔案中配置了Engine和Host。

<Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
複製程式碼

那麼這四種容器之間是如何協同工作的呢?Connector將一個連線請求交給Container之後,這四類容器之間如何分工合作,怎麼將請求交給特定的子容器進行處理,即一個請求是如何從Engine最終對映到一個具體的servlet的?先介紹一下整體運作流程,如下面的時序圖所示:

tomcat原始碼分析(第四篇 tomcat請求處理原理解析--Container原始碼分析)

從上圖可以看出,每個Container容器都有對應的閥Valve,多個Valve組成了Pipeline,這就是Container的具體實現過程,也可以在server.xml檔案中配置Pipeline和Valve的集合實現。管道Pipe包含了容器中要執行的任務,而每一個閥Valve表示一個具體的任務,在每個管道中,都會有一個預設的閥,可以新增任意數量的閥,可通過server.xml檔案配置。對過濾器熟悉的話就會發現,管道和閥的工作機制和過濾器工作機制相似,Pipeline相當於過濾器鏈FilterChain,Valve相當於每一個過濾器Filter。閥可以處理傳給它的request物件和response物件,處理完一個Valve後接著處理下一個Valve,最後處理的閥是基礎閥。下面就追蹤每一個容器的管道,解析容器處理請求的流程

首先是Engine容器,預設實現是StandardEngine,建立StandardEngine時例項化其基礎閥,程式碼如下

    public StandardEngine() {
        super();
        //設定基礎閥StandardEngineValve
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

複製程式碼

繼續跟蹤StandardEngineValve的invoke()方法,原始碼為:

    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // 選出和該request相關的Host,在MappingData中儲存了請求和容器(Host,Context,Wrapper)之間的對映
        Host host = request.getHost();
        if (host == null) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost",
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // host.getPipeline()得到Host對應的管道Pipeline,將request和response物件交給Host的閥去處理
        host.getPipeline().getFirst().invoke(request, response);

複製程式碼

StandardEngineValve的invoke()方法是在CoyoteAdapter類中呼叫的,也就是Connector將請求交給Container的過程:

        //connector.getService().getContainer()得到Connector關聯的Container,然後將request和response物件交給Engine的管道Pineline中的閥去處理。
        if (!request.isAsyncDispatching() && request.isAsync() &&
                response.isErrorReportRequired()) {
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        if (request.isAsyncDispatching()) {
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
            Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
            if (t != null) {
                asyncConImpl.setErrorState(t, true);
            }
        }

複製程式碼

同樣Host容器構造器中設定了其基礎閥StandardHostValve

    public StandardHost() {
        super();
        pipeline.setBasic(new StandardHostValve());
    }
複製程式碼

同樣跟蹤StandardHostValve的invoke方法

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // 該request容器關聯的Context,儲存在MappingData中
        Context context = request.getContext();
        if (context == null) {
            return;
        }
        //是否支援非同步
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(context.getPipeline().isAsyncSupported());
        }
        boolean asyncAtStart = request.isAsync();
        try {
            //設定StandardHostValve的類載入器
            context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
            if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) {
                return;
            }

            // 將request傳遞給Context的閥去處理,有錯誤的頁面必須在此處處理,不會繼續向下傳遞到Context容器中
            try {
                if (!response.isErrorReportRequired()) {
                    context.getPipeline().getFirst().invoke(request, response);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
                // If a new error occurred while trying to report a previous
                // error allow the original error to be reported.
                if (!response.isErrorReportRequired()) {
                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                    throwable(request, response, t);
                }
            }

            // Now that the request/response pair is back under container
            // control lift the suspension so that the error handling can
            // complete and/or the container can flush any remaining data
            response.setSuspended(false);

            Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

            // Protect against NPEs if the context was destroyed during a
            // long running request.
            if (!context.getState().isAvailable()) {
                return;
            }
            //設定錯誤頁面
            if (response.isErrorReportRequired()) {
                if (t != null) {
                    throwable(request, response, t);
                } else {
                    status(request, response);
                }
            }

            if (!request.isAsync() && !asyncAtStart) {
                context.fireRequestDestroyEvent(request.getRequest());
            }
        } finally {
            // Access a session (if present) to update last accessed time, based
            // on a strict interpretation of the specification
            if (ACCESS_SESSION) {
                request.getSession(false);
            }

            context.unbind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
        }
    }
複製程式碼

Context和Wrapper的管道和閥的實現過程與Engine和Host完全一樣,不再繼續分析。最後主要解析StandardHostValve的invoke()方法,看該方法如何將request交個一個servlet處理。鑑於該方法原始碼太長,只展示出了部分重要程式碼。

    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        ...
        //獲取關聯的StandardWrapper
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        //wrapper的父容器Context
        Context context = (Context) wrapper.getParent();
        ...
        // 分配一個servlet例項處理該request
        try {
            //servlet可用時,分配servlet,接下來會跟蹤allocate()方法
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } catch (UnavailableException e) {
            //分別設定了503錯誤和404 not found
            ...
            }
        } catch (ServletException e) {
            ...
        } catch (Throwable e) {
            ...
        }
        // 為該request設定過濾器
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        // 過濾器作用於該request,並且此過程中呼叫了servlet的service()方法
        try {
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            request.getAsyncContextInternal().doInternalDispatch();
                        } else {
                            filterChain.doFilter(request.getRequest(),
                                    response.getResponse());
                        }
                    } finally {
                        String log = SystemLogHandler.stopCapture();
                        if (log != null && log.length() > 0) {
                            context.getLogger().info(log);
                        }
                    }
                } else {
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
        } catch (ClientAbortException e) {
            ...
        } catch (IOException e) {
            ...
        } catch (UnavailableException e) {
            ...
        } catch (ServletException e) {
            ...
        } catch (Throwable e) {
            ...
        }

        // 釋放該request的過濾鏈
        if (filterChain != null) {
            filterChain.release();
        }
        // 回收servlet容器例項
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
           ...
        }
        ...
    }

複製程式碼

接著跟蹤Wrapper的allocate原始碼:該方法主要功能是分配一個初始化了的servlet例項,其service方法可以被呼叫。

    public Servlet allocate() throws ServletException {
        // servlet類沒有載入時剖出異常
        if (unloading) {
            throw new ServletException(sm.getString("standardWrapper.unloading", getName()));
        }
        boolean newInstance = false;
        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {
            // servlet沒有載入時要先載入該servlet
            if (instance == null || !instanceInitialized) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            ...
                            //載入servlet,接下來繼續分析loadServlet()方法
                            instance = loadServlet();
                            newInstance = true;
                            //類載入之前並不知道該servlet是否為singleThreadModel,在loadServlet()中會改變singleThreadModel的值,所以此處要再判斷一次
                            if (!singleThreadModel) {
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ...
                        }
                    }
                    if (!instanceInitialized) {
                        //初始化servlet
                        initServlet(instance);
                    }
                }
            }
            //新載入的servlet實現singleThreadModel時將instance加入到instancePool中,否則直接返回instance
            if (singleThreadModel) {
                if (newInstance) {
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Returning non-STM instance");
                }
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return instance;
            }
        }
        //SingleThreadedModel型別的servlet時返回instancePool中的一個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) {
                       ...
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            if (log.isTraceEnabled()) {
                log.trace("  Returning allocated STM instance");
            }
            countAllocated.incrementAndGet();
            return instancePool.pop();
        }
    }
複製程式碼

接下來看一下servlet的load過程

    public synchronized Servlet loadServlet() throws ServletException {
        // 如果不是SingleThreadModel型別的servlet,並且已經存在一個instance例項時,不需要載入。
        if (!singleThreadModel && (instance != null))
            return instance;
        ...
        Servlet servlet;
        try {
            ...
            //Context容器中的instanceManager,是一個類載入器,其newInstance方法根據class路徑載入servlet
            InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
            try {
                servlet = (Servlet) instanceManager.newInstance(servletClass);
            } catch (ClassCastException e) {
                ...
            } catch (Throwable e) {
                ...
            }
            if (multipartConfigElement == null) {
                MultipartConfig annotation =
                        servlet.getClass().getAnnotation(MultipartConfig.class);
                if (annotation != null) {
                    multipartConfigElement =
                            new MultipartConfigElement(annotation);
                }
            }

            // Special handling for ContainerServlet instances
            // Note: The InstanceManager checks if the application is permitted
            //       to load ContainerServlets
            if (servlet instanceof ContainerServlet) {
                ((ContainerServlet) servlet).setWrapper(this);
            }
            classLoadTime=(int) (System.currentTimeMillis() -t1);
            if (servlet instanceof SingleThreadModel) {
                if (instancePool == null) {
                    instancePool = new Stack<>();
                }
                singleThreadModel = true;    //此處修改了singleThreadModel值,所以allocate方法中新載入servlet類後要重新判斷這個值
            }
            initServlet(servlet);  //初始化剛載入的servlet
            fireContainerEvent("load", this);
            loadTime=System.currentTimeMillis() -t1;
        } finally {
            if (swallowOutput) {
                String log = SystemLogHandler.stopCapture();
                if (log != null && log.length() > 0) {
                    if (getServletContext() != null) {
                        getServletContext().log(log);
                    } else {
                        out.println(log);
                    }
                }
            }
        }
        return servlet;

    }
複製程式碼

通過以上分析,我們知道了一個request請求是如何從Engine容器一路流動到了具體處理容器Wrapper中的,就是通過管道和閥的工作機制實現的,每一個容器都會對應一個管道,可以向管道中新增任意數量的閥valve,但必須要有一個基礎閥,上一層的容器通過呼叫下一次容器的管道的閥的invoke方法實現request物件的傳遞。

tomcat原始碼分析(第一篇 tomcat原始碼分析(第一篇 從整體架構開始))
tomcat原始碼分析(第二篇 tomcat啟動過程詳解)
tomcat原始碼分析(第三篇 tomcat請求原理解析--Connector原始碼分析)

相關文章