tomcat源碼分析(第四篇 tomcat請(qǐng)求處理原理解析--Container源碼分析)

Container容器是所用servlet容器的父接口,也就是說作為一個(gè)servlet容器选脊,首先必須要實(shí)現(xiàn)Container接口逆济,每個(gè)tomcat服務(wù)器只能有唯一的根Container,Connector組件通過setContainer方法將Container容器和Connector關(guān)聯(lián)起來腿倚。共有四種類型Container容器囤萤,分別對(duì)應(yīng)不同概念的層次昼窗,每一層之間是父子的關(guān)系。

1阁将、Engine:整個(gè)Catalina servlet引擎,標(biāo)準(zhǔn)實(shí)現(xiàn)為StandardEngine膏秫。

2、Host:表示包含一個(gè)或多個(gè)Context容器的虛擬主機(jī),標(biāo)準(zhǔn)實(shí)現(xiàn)為StandardHost缤削。

3窘哈、Context:表示一個(gè)web應(yīng)用程序,一個(gè)Context可以有多個(gè)Wrapper亭敢,標(biāo)準(zhǔn)實(shí)現(xiàn)為StandardContext滚婉。

4、Wrapper:包裝一個(gè)獨(dú)立的Servlet容器帅刀,標(biāo)準(zhǔn)實(shí)現(xiàn)為StandardWrapper让腹。

在第二節(jié)的分析中我們知道,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>

那么這四種容器之間是如何協(xié)同工作的呢骇窍?Connector將一個(gè)連接請(qǐng)求交給Container之后,這四類容器之間如何分工合作锥余,怎么將請(qǐng)求交給特定的子容器進(jìn)行處理腹纳,即一個(gè)請(qǐng)求是如何從Engine最終映射到一個(gè)具體的servlet的?先介紹一下整體運(yùn)作流程驱犹,如下面的時(shí)序圖所示:

image

從上圖可以看出嘲恍,每個(gè)Container容器都有對(duì)應(yīng)的閥Valve,多個(gè)Valve組成了Pipeline雄驹,這就是Container的具體實(shí)現(xiàn)過程佃牛,也可以在server.xml文件中配置Pipeline和Valve的集合實(shí)現(xiàn)。管道Pipe包含了容器中要執(zhí)行的任務(wù)医舆,而每一個(gè)閥Valve表示一個(gè)具體的任務(wù)俘侠,在每個(gè)管道中,都會(huì)有一個(gè)默認(rèn)的閥蔬将,可以添加任意數(shù)量的閥兼贡,可通過server.xml文件配置。對(duì)過濾器熟悉的話就會(huì)發(fā)現(xiàn)娃胆,管道和閥的工作機(jī)制和過濾器工作機(jī)制相似遍希,Pipeline相當(dāng)于過濾器鏈FilterChain,Valve相當(dāng)于每一個(gè)過濾器Filter里烦。閥可以處理傳給它的request對(duì)象和response對(duì)象凿蒜,處理完一個(gè)Valve后接著處理下一個(gè)Valve,最后處理的閥是基礎(chǔ)閥胁黑。下面就追蹤每一個(gè)容器的管道废封,解析容器處理請(qǐng)求的流程

首先是Engine容器,默認(rèn)實(shí)現(xiàn)是StandardEngine丧蘸,創(chuàng)建StandardEngine時(shí)實(shí)例化其基礎(chǔ)閥漂洋,代碼如下

    public StandardEngine() {
        super();
        //設(shè)置基礎(chǔ)閥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;

    }

繼續(xù)跟蹤StandardEngineValve的invoke()方法,源碼為:

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

        // 選出和該request相關(guān)的Host,在MappingData中保存了請(qǐng)求和容器(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對(duì)應(yīng)的管道Pipeline刽漂,將request和response對(duì)象交給Host的閥去處理
        host.getPipeline().getFirst().invoke(request, response);

StandardEngineValve的invoke()方法是在CoyoteAdapter類中調(diào)用的演训,也就是Connector將請(qǐng)求交給Container的過程:

        //connector.getService().getContainer()得到Connector關(guān)聯(lián)的Container,然后將request和response對(duì)象交給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容器構(gòu)造器中設(shè)置了其基礎(chǔ)閥StandardHostValve

    public StandardHost() {
        super();
        pipeline.setBasic(new StandardHostValve());
    }

同樣跟蹤StandardHostValve的invoke方法

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

        // 該request容器關(guān)聯(lián)的Context样悟,保存在MappingData中
        Context context = request.getContext();
        if (context == null) {
            return;
        }
        //是否支持異步
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(context.getPipeline().isAsyncSupported());
        }
        boolean asyncAtStart = request.isAsync();
        try {
            //設(shè)置StandardHostValve的類加載器
            context.bind(Globals.IS_SECURITY_ENABLED, MY_CLASSLOADER);
            if (!asyncAtStart && !context.fireRequestInitEvent(request.getRequest())) {
                return;
            }

            // 將request傳遞給Context的閥去處理,有錯(cuò)誤的頁面必須在此處處理庭猩,不會(huì)繼續(xù)向下傳遞到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;
            }
            //設(shè)置錯(cuò)誤頁面
            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的管道和閥的實(shí)現(xiàn)過程與Engine和Host完全一樣窟她,不再繼續(xù)分析。最后主要解析StandardHostValve的invoke()方法蔼水,看該方法如何將request交個(gè)一個(gè)servlet處理震糖。鑒于該方法源碼太長(zhǎng),只展示出了部分重要代碼趴腋。

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

        // 過濾器作用于該request于样,并且此過程中調(diào)用了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容器實(shí)例
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
           ...
        }
        ...
    }

接著跟蹤Wrapper的allocate源碼:該方法主要功能是分配一個(gè)初始化了的servlet實(shí)例,其service方法可以被調(diào)用潘靖。

    public Servlet allocate() throws ServletException {
        // servlet類沒有加載時(shí)剖出異常
        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沒有加載時(shí)要先載入該servlet
            if (instance == null || !instanceInitialized) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            ...
                            //加載servlet穿剖,接下來繼續(xù)分析loadServlet()方法
                            instance = loadServlet();
                            newInstance = true;
                            //類加載之前并不知道該servlet是否為singleThreadModel,在loadServlet()中會(huì)改變singleThreadModel的值卦溢,所以此處要再判斷一次
                            if (!singleThreadModel) {
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ...
                        }
                    }
                    if (!instanceInitialized) {
                        //初始化servlet
                        initServlet(instance);
                    }
                }
            }
            //新加載的servlet實(shí)現(xiàn)singleThreadModel時(shí)將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時(shí)返回instancePool中的一個(gè)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单寂,并且已經(jīng)存在一個(gè)instance實(shí)例時(shí)贬芥,不需要加載。
        if (!singleThreadModel && (instance != null))
            return instance;
        ...
        Servlet servlet;
        try {
            ...
            //Context容器中的instanceManager宣决,是一個(gè)類加載器蘸劈,其newInstance方法根據(jù)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類后要重新判斷這個(gè)值
            }
            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;

    }

通過以上分析尊沸,我們知道了一個(gè)request請(qǐng)求是如何從Engine容器一路流動(dòng)到了具體處理容器Wrapper中的威沫,就是通過管道和閥的工作機(jī)制實(shí)現(xiàn)的,每一個(gè)容器都會(huì)對(duì)應(yīng)一個(gè)管道洼专,可以向管道中添加任意數(shù)量的閥valve棒掠,但必須要有一個(gè)基礎(chǔ)閥,上一層的容器通過調(diào)用下一次容器的管道的閥的invoke方法實(shí)現(xiàn)request對(duì)象的傳遞屁商。

tomcat源碼分析(第一篇 tomcat源碼分析(第一篇 從整體架構(gòu)開始))

tomcat源碼分析(第二篇 tomcat啟動(dòng)過程詳解)

tomcat源碼分析(第三篇 tomcat請(qǐng)求原理解析--Connector源碼分析)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烟很,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雾袱,老刑警劉巖恤筛,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谜酒,居然都是意外死亡叹俏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門僻族,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘驰,“玉大人,你說我怎么就攤上這事述么◎蚴” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵度秘,是天一觀的道長(zhǎng)顶伞。 經(jīng)常有香客問我,道長(zhǎng)剑梳,這世上最難降的妖魔是什么唆貌? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮垢乙,結(jié)果婚禮上锨咙,老公的妹妹穿的比我還像新娘。我一直安慰自己追逮,他們只是感情好酪刀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钮孵,像睡著了一般骂倘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巴席,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天历涝,我揣著相機(jī)與錄音,去河邊找鬼漾唉。 笑死睬关,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毡证。 我是一名探鬼主播电爹,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼料睛!你這毒婦竟也來了丐箩?” 一聲冷哼從身側(cè)響起摇邦,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屎勘,沒想到半個(gè)月后施籍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡概漱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年丑慎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓤摧。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竿裂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出照弥,到底是詐尸還是另有隱情腻异,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布这揣,位于F島的核電站悔常,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏给赞。R本人自食惡果不足惜机打,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望片迅。 院中可真熱鬧残邀,春花似錦、人聲如沸障涯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唯蝶。三九已至,卻和暖如春遗嗽,著一層夾襖步出監(jiān)牢的瞬間粘我,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工痹换, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留征字,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓娇豫,卻偏偏與公主長(zhǎng)得像匙姜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冯痢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 這篇文章講的是需要知識(shí)學(xué)習(xí)了解之后的學(xué)以致用氮昧,同時(shí)加以控制練習(xí)大腦框杜。 但是又比如說:你看的許多互聯(lián)網(wǎng)大咖書,有些沒...
    lvylh閱讀 459評(píng)論 0 3
  • 菠蘿的纖維在上顎摩擦 淡淡的血味和汁液混雜 我嗅到的苦味 Cherry撩起褲腳對(duì)著鏡子羞澀的照 齒間糾纏袖肥,酸 我腳...
    青俞閱讀 94評(píng)論 0 0
  • 畏懼死亡比死亡更可怕咪辱。——赤井秀一《名偵探柯南》 死亡其實(shí)不可怕椎组,可怕的是我們?nèi)绾慰创涂瘛_@觀點(diǎn)在動(dòng)畫片柯南里面都...
    楚浛閱讀 523評(píng)論 0 5