Tomcat請(qǐng)求響應(yīng)處理(二)

前言
在上半部分我們分析了Tomcat請(qǐng)求響應(yīng)的生成過程,以及對(duì)應(yīng)請(qǐng)求容器的映射過程刘离,就像客人去朋友家小聚室叉,首先肯定要知道朋友的地址和門牌號(hào)碼,知道之后當(dāng)然就要敲門進(jìn)去硫惕,一陣吃喝吹吹牛逼茧痕,再從朋友家出來返回。地址和門牌號(hào)碼對(duì)應(yīng)請(qǐng)求映射關(guān)系恼除,吃吃喝喝對(duì)應(yīng)Servlet處理業(yè)務(wù)邏輯踪旷,回家對(duì)應(yīng)返回response。本文就對(duì)下半部分:根據(jù)門牌號(hào)碼找到朋友家再返回的過程進(jìn)行解析

Tomcat請(qǐng)求響應(yīng)處理(一)的最后豁辉,我們看到一個(gè)很長的鏈?zhǔn)秸{(diào)用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)令野,我們一點(diǎn)點(diǎn)來分析一下。connector.getService()得到Connector的父容器StandardService徽级,由于StandardService是連接Tomcat兩大組件的橋梁气破,自然getContainer()又可以得到Container頂層容器StandardEngine,在Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)中說過每個(gè)Container都存在一個(gè)StandardPipeline管道餐抢,每個(gè)管道中存在一個(gè)或者多個(gè)Valve閥門现使。當(dāng)請(qǐng)求來時(shí)會(huì)按照容器的父子關(guān)系依次流入一個(gè)個(gè)管道,遇到管道中一個(gè)個(gè)閥門旷痕,既然管道有順序碳锈,里面的閥門也有順序,getPipeline().getFirst()就對(duì)應(yīng)著第一個(gè)閥門StandardEngineValve欺抗,進(jìn)而調(diào)用其invoke(Request, Response)

圖1. StandardEngineValve的invoke(Request, Response)

由于參數(shù)request中已經(jīng)保存了正確的“門牌號(hào)碼”殴胧,自然能得到請(qǐng)求對(duì)應(yīng)的虛擬主機(jī)StandardHost,如果此時(shí)該對(duì)象為空自然有問題,將錯(cuò)誤碼塞入response中返回团滥,最后責(zé)任鏈模式再次出現(xiàn)竿屹,調(diào)用StandardHost中管道的第一個(gè)閥門,默認(rèn)情況下在server.xml中存在一個(gè)Valve灸姊,對(duì)應(yīng)的實(shí)體為AccessLogValve拱燃,主要用來記錄該虛擬主機(jī)的訪問情況
圖2. AccessLogValve的invoke(Request, Response)

其中并沒有做其他的操作,僅僅調(diào)用了管道中下一個(gè)閥門力惯,下一個(gè)閥門依然不是基礎(chǔ)閥門碗誉,在StandardHost啟動(dòng)時(shí)Tomcat又為其添加了另一個(gè)“錯(cuò)誤上報(bào)閥門”
圖3. StandardHost的startInternal()

getErrorReportValveClass()返回該閥門對(duì)應(yīng)全路徑字符串org.apache.catalina.valves.ErrorReportValve,當(dāng)管道中不存在對(duì)應(yīng)名稱的閥門就將該閥門加入管道中
圖4. ErrorReportValve的invoke()

該閥門的第一句直接調(diào)用了下一個(gè)閥門父晶,我們可以把該閥門的功能理解為spring中的后置增強(qiáng)哮缺,即在響應(yīng)之后再進(jìn)行某些操作,因?yàn)樵撻y門是用來記錄處理請(qǐng)求中產(chǎn)生錯(cuò)誤的甲喝,而上面說過尝苇,當(dāng)流程中發(fā)生錯(cuò)誤會(huì)存在一個(gè)對(duì)應(yīng)的錯(cuò)誤碼,而該錯(cuò)誤碼又封裝在response中埠胖,那這里就不難理解為什么要在調(diào)用鏈返回過程中再做處理糠溜。下一個(gè)閥門就是StandardHost的基礎(chǔ)閥門StandardHostValve代碼清單1

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

    // Select the Context to be used for this Request
    Context context = request.getContext();
    if (context == null) {
        response.sendError
            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
             sm.getString("standardHost.noContext"));
        return;
    }

    // Bind the context CL to the current thread
    if( context.getLoader() != null ) {
        // Not started - it should check for availability first
        // This should eventually move to Engine, it's generic.
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    context.getLoader().getClassLoader());
            AccessController.doPrivileged(pa);                
        } else {
            Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
        }
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }
    //    (1)
    boolean asyncAtStart = request.isAsync();
    boolean asyncDispatching = request.isAsyncDispatching();
    if (asyncAtStart || context.fireRequestInitEvent(request)) {

        // Ask this Context to process this request. Requests that are in
        // async mode and are not being dispatched to this resource must be
        // in error and have been routed here to check for application
        // defined error pages.
        try {
            if (!asyncAtStart || asyncDispatching) {
                //    (2)
                context.getPipeline().getFirst().invoke(request, response);
            } else {
                // Make sure this request/response is here because an error
                // report is required.
                if (!response.isErrorReportRequired()) {
                    throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
                }
            }
        } 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;
        }

        // Look for (and render if found) an application level error page
        if (response.isErrorReportRequired()) {
            if (t != null) {
                throwable(request, response, t);
            } else {
                status(request, response);
            }
        }

        if (!request.isAsync() && (!asyncAtStart || !response.isErrorReportRequired())) {
            context.fireRequestDestroyEvent(request);
        }
    }

    // Access a session (if present) to update last accessed time, based on a
    // strict interpretation of the specification
    if (ACCESS_SESSION) {
        request.getSession(false);
    }

    // Restore the context classloader
    if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                StandardHostValve.class.getClassLoader());
        AccessController.doPrivileged(pa);                
    } else {
        Thread.currentThread().setContextClassLoader
                (StandardHostValve.class.getClassLoader());
    }
}

標(biāo)注(1)下的兩行代碼判斷該請(qǐng)求是否異步直撤,默認(rèn)為false非竿,Context.fireRequestInitEvent(Request),該方法內(nèi)封裝了ServletRequestEvent事件谋竖,并由一系列的應(yīng)用事件監(jiān)聽器applicationEventListenersObjects負(fù)責(zé)處理红柱,同樣默認(rèn)不存在具體的監(jiān)聽器,返回true蓖乘,導(dǎo)致代碼走到標(biāo)注(2)處锤悄,再一次責(zé)任鏈調(diào)用StandardContext內(nèi)的第一個(gè)閥門

圖5. StandardContextValve的invoke()

看到第一個(gè)if判斷對(duì)/META-INF//WEB-INF目錄下的資源進(jìn)行了過濾,記得我剛學(xué)web編程時(shí)老師讓我們把自定義的Servlet放在/WEB-INF下驱敲,說是不讓直接訪問只能內(nèi)部跳轉(zhuǎn)從而保證安全铁蹈,那不能訪問的秘密就是這段代碼了。之后調(diào)用請(qǐng)求映射的Wrapper众眨,進(jìn)而invoke管道中的第一個(gè)閥門握牧,對(duì)應(yīng)的類為StandardWrapperValve
圖6. StandardWrapperValve的invoke()

圖中我刪除了很多非重點(diǎn)代碼,并將主要流程分成兩部分娩梨,第一紅框域具體的Servlet有關(guān)沿腰,第二個(gè)與該Servlet相關(guān)的過濾器有關(guān)。我們從第一個(gè)開始分析狈定,wrapper.allocate()最終會(huì)調(diào)用StandardWrapper.loadServlet()颂龙,代碼清單2

public synchronized Servlet loadServlet() throws ServletException {

    if (unloading) {
        throw new ServletException(
                sm.getString("standardWrapper.unloading", getName()));
    }
    //    (1)
    // Nothing to do if we already have an instance or an instance pool
    if (!singleThreadModel && (instance != null))
        return instance;

    PrintStream out = System.out;
    if (swallowOutput) {
        SystemLogHandler.startCapture();
    }

    Servlet servlet;
    try {
        long t1=System.currentTimeMillis();
        // Complain if no servlet class has been specified
        if (servletClass == null) {
            unavailable(null);
            throw new ServletException
                (sm.getString("standardWrapper.notClass", getName()));
        }

        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            //    (2)
            servlet = (Servlet) instanceManager.newInstance(servletClass);

        } catch (ClassCastException e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.notServlet", servletClass), e);
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            unavailable(null);

            // Added extra log statement for Bugzilla 36630:
            // http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
            if(log.isDebugEnabled()) {
                log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.instantiate", servletClass), e);
        }
        //    (3)
        if (multipartConfigElement == null) {
            MultipartConfig annotation =
                    servlet.getClass().getAnnotation(MultipartConfig.class);
            if (annotation != null) {
                multipartConfigElement =
                        new MultipartConfigElement(annotation);
            }
        }

        processServletSecurityAnnotation(servlet.getClass());

        // Special handling for ContainerServlet instances
        if ((servlet instanceof ContainerServlet) &&
                (isContainerProvidedServlet(servletClass) ||
                        ((Context) getParent()).getPrivileged() )) {
            ((ContainerServlet) servlet).setWrapper(this);
        }

        classLoadTime=(int) (System.currentTimeMillis() -t1);

        if (servlet instanceof SingleThreadModel) {
            if (instancePool == null) {
                instancePool = new Stack<Servlet>();
            }
            singleThreadModel = true;
        }
        //    (4)
        initServlet(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;

}

標(biāo)注(1)判斷該Servlet是否為單例习蓬,默認(rèn)Servlet是多例的,如果實(shí)現(xiàn)一個(gè)過時(shí)的SingleThreadModel標(biāo)記接口措嵌,Tomcat就會(huì)將標(biāo)識(shí)singleThreadModel置為true躲叼,而這里就會(huì)直接返回;否則進(jìn)入標(biāo)注(2)根據(jù)解析的servletClass反射創(chuàng)建出用戶自己配置的Servlet企巢。標(biāo)注(3)是在Servlet3.0中引入的注解形式的文件上傳方式校驗(yàn)枫慷,標(biāo)注(4)最終會(huì)調(diào)用Servletinit(ServletConfig),該方法GenericServlet.init(ServletConfig)浪规,內(nèi)部又調(diào)用了一個(gè)無參的init()或听,當(dāng)我們創(chuàng)建Servlet時(shí)可以覆寫該方法,從而在第一次調(diào)用Servlet時(shí)進(jìn)行一些初始化的操作
我們回到圖6中的第二個(gè)紅框笋婿,代碼使用工廠創(chuàng)建了一個(gè)過濾器鏈filterChain誉裆,具體創(chuàng)建代碼如 代碼清單3

public ApplicationFilterChain createFilterChain
    (ServletRequest request, Wrapper wrapper, Servlet servlet) {

    // get the dispatcher type
    DispatcherType dispatcher = null;
    if (request.getAttribute(Globals.DISPATCHER_TYPE_ATTR) != null) {
        dispatcher = (DispatcherType) request.getAttribute(
                Globals.DISPATCHER_TYPE_ATTR);
    }
    String requestPath = null;
    Object attribute = request.getAttribute(
            Globals.DISPATCHER_REQUEST_PATH_ATTR);

    if (attribute != null){
        requestPath = attribute.toString();
    }

    // If there is no servlet to execute, return null
    if (servlet == null)
        return (null);

    boolean comet = false;

    // Create and initialize a filter chain object
    ApplicationFilterChain filterChain = null;
    if (request instanceof Request) {
        Request req = (Request) request;
        comet = req.isComet();
        if (Globals.IS_SECURITY_ENABLED) {
            // Security: Do not recycle
            filterChain = new ApplicationFilterChain();
            if (comet) {
                req.setFilterChain(filterChain);
            }
        } else {
            //    (1)
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = new ApplicationFilterChain();
                req.setFilterChain(filterChain);
            }
        }
    } else {
        // Request dispatcher in use
        filterChain = new ApplicationFilterChain();
    }
    //    (2)
    filterChain.setServlet(servlet);

    filterChain.setSupport
        (((StandardWrapper)wrapper).getInstanceSupport());

    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    // If there are no filter mappings, we are done
    if ((filterMaps == null) || (filterMaps.length == 0))
        return (filterChain);

    // Acquire the information we will need to match filter mappings
    String servletName = wrapper.getName();

    // Add the relevant path-mapped filters to this filter chain
    //    (3)
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of
                // declared exceptions. However, the filter is allocated much
                // earlier
                Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(t);
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
            filterChain.addFilter(filterConfig);
        }
    }

    // Add filters that match on servlet name second
    //    (4)
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMaps[i], servletName))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of
                // declared exceptions. However, the filter is allocated much
                // earlier
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
            filterChain.addFilter(filterConfig);
        }
    }

    // Return the completed filter chain
    return (filterChain);

}

標(biāo)注(1)和(2)創(chuàng)建出過濾器鏈并將請(qǐng)求與之對(duì)應(yīng)的Servlet與之關(guān)聯(lián),通過StandardContext得到web.xml中配置的所有<filter-mapping>對(duì)應(yīng)的實(shí)體FilterMap數(shù)組缸濒,該數(shù)組中的數(shù)據(jù)來源逆序調(diào)用為StandardContext -> addFilterMap(FilterMap) -> WebXml.configureContext(Context) -> ContextConfig.webConfig() -> ContextConfig.configureStart() -> ContextConfig檢測(cè)到Lifecycle.CONFIGURE_START_EVENT事件足丢,具體的流程分析可以參考Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)。還有一點(diǎn)需要注意的是绍填,每一個(gè)過濾器都存在一個(gè)類型為DispatcherType的調(diào)度類型dispatcher霎桅,表示該過濾器對(duì)哪一種請(qǐng)求類型進(jìn)行攔截栖疑,共有FORWORD讨永、INCLUDEREQUEST遇革、ASYNC卿闹、ERROR五種類型,默認(rèn)為REQUEST
標(biāo)注(3)遍歷所有的過濾器數(shù)組萝快,進(jìn)行兩輪匹配判斷锻霎,第一輪matchDispatcher(FilterMap, DispatcherType)判斷是否filter配置的調(diào)度類型在上述五種類型之中;第二輪matchFiltersURL(FilterMap, String)判斷請(qǐng)求URL是否命中一個(gè)filter揪漩,如若存在一個(gè)匹配過濾器旋恼,那么根據(jù)對(duì)應(yīng)filter名稱從StandardContext中的成員變量HashMap<String, ApplicationFilterConfig> filterConfigs中得到對(duì)應(yīng)的實(shí)例ApplicationFilterConfig。之前的文章曾經(jīng)分析過在解析web.xml時(shí)奄容,過濾器對(duì)應(yīng)的對(duì)象為FilterDef冰更,但在StandardContext啟動(dòng)時(shí)中間有一步是啟動(dòng)所有的過濾器,此時(shí)會(huì)將所有的FilterDef轉(zhuǎn)成ApplicationFilterConfig放入該Map
要理解標(biāo)注(4)必須先回憶一下<filter-mapping>配置的方式昂勒,要攔截請(qǐng)求其實(shí)有兩種方式:1.配置<url-pattern>過濾請(qǐng)求路徑蜀细;2.配置<servlet-name>過濾特定Servlet,那代碼中的兩個(gè)for循環(huán)就對(duì)應(yīng)兩種方式了戈盈。最后將請(qǐng)求路徑或者請(qǐng)求對(duì)應(yīng)Servlet的過濾器通過addFilter(filterConfig)加入到ApplicationFilterChain中成員變量filters數(shù)組中
回到圖6第二個(gè)紅框中最后一句奠衔,終于見到了我們熟悉的doFilter(Request, Response)谆刨,內(nèi)部最終走到ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse),見代碼清單4

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

    // Call the next filter if there is one
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        Filter filter = null;

        try {
            filter = filterConfig.getFilter();
            support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                    filter, request, response);

            if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                    filterConfig.getFilterDef().getAsyncSupported())) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            if (Globals.IS_SECURITY_ENABLED) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[] {req, res, this};
                SecurityUtil.doAsPrivilege
                        ("doFilter", filter, classType, args, principal);

            } else {
                filter.doFilter(request, response, this);
            }

            support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                    filter, request, response);
        } catch (IOException e) {
            if (filter != null) {
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                        filter, request, response, e);
            }
            throw e;
        } catch (ServletException e) {
            if (filter != null) {
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                        filter, request, response, e);
            }
            throw e;
        } catch (RuntimeException e) {
            if (filter != null) {
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                        filter, request, response, e);
            }
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            if (filter != null) {
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                        filter, request, response, e);
            }
            throw new ServletException
                    (sm.getString("filterChain.filter"), e);
        }
        return;
    }

    // We fell off the end of the chain -- call the servlet instance
    try {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(request);
            lastServicedResponse.set(response);
        }

        support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                servlet, request, response);
        if (request.isAsyncSupported()
                && !support.getWrapper().isAsyncSupported()) {
            request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                    Boolean.FALSE);
        }

        // Use potentially wrapped request from this point
        if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {

            if (Globals.IS_SECURITY_ENABLED) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[] {req, res};
                SecurityUtil.doAsPrivilege("service",
                        servlet,
                        classTypeUsedInService,
                        args,
                        principal);
            } else {
                servlet.service(request, response);
            }
        } else {
            servlet.service(request, response);
        }
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                servlet, request, response);
    } catch (IOException e) {
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                servlet, request, response, e);
        throw e;
    } catch (ServletException e) {
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                servlet, request, response, e);
        throw e;
    } catch (RuntimeException e) {
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                servlet, request, response, e);
        throw e;
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                servlet, request, response, e);
        throw new ServletException
                (sm.getString("filterChain.servlet"), e);
    } finally {
        if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
            lastServicedRequest.set(null);
            lastServicedResponse.set(null);
        }
    }

}

雖然代碼比較長但是結(jié)構(gòu)還是很清晰的归斤,主要分為上下兩部分痊夭。第一部分由整個(gè)if塊包裹,pos是當(dāng)前遍歷ApplicationFilter元素對(duì)應(yīng)的數(shù)組下標(biāo)脏里,n為整個(gè)數(shù)組長度生兆,總的意思就是遍歷過濾器數(shù)組中每一個(gè)filter,并依次調(diào)用它的doFilter(ServletRequest, ServletResponse)膝宁,該方法就由我們自己實(shí)現(xiàn)了鸦难。當(dāng)所有的filter處理完畢走到第二部分,就調(diào)用serlvet.service(request, response)员淫。至此Tomcat整個(gè)請(qǐng)求響應(yīng)處理的過程分析完畢

后記
對(duì)于Tomcat源碼的解析暫時(shí)告一段落了合蔽,我從整個(gè)過程中學(xué)到了很多。好的代碼就像好的作文一樣介返,對(duì)程序員的影響是潛移默化的拴事,很難想象一個(gè)從來不看范文的人能寫出多好的文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市圣蝎,隨后出現(xiàn)的幾起案子刃宵,更是在濱河造成了極大的恐慌,老刑警劉巖徘公,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲证,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡关面,警方通過查閱死者的電腦和手機(jī)坦袍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來等太,“玉大人捂齐,你說我怎么就攤上這事∷趼眨” “怎么了奠宜?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞻想。 經(jīng)常有香客問我压真,道長,這世上最難降的妖魔是什么内边? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任榴都,我火速辦了婚禮,結(jié)果婚禮上漠其,老公的妹妹穿的比我還像新娘嘴高。我一直安慰自己竿音,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布拴驮。 她就那樣靜靜地躺著春瞬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪套啤。 梳的紋絲不亂的頭發(fā)上宽气,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音潜沦,去河邊找鬼萄涯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唆鸡,可吹牛的內(nèi)容都是我干的涝影。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼争占,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼燃逻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臂痕,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤伯襟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后握童,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姆怪,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年舆瘪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了片效。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片红伦。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡英古,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昙读,到底是詐尸還是另有隱情召调,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布蛮浑,位于F島的核電站唠叛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沮稚。R本人自食惡果不足惜艺沼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蕴掏。 院中可真熱鬧障般,春花似錦调鲸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至定拟,卻和暖如春于微,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背青自。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工株依, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人延窜。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓勺三,卻偏偏與公主長得像,于是被迫代替她去往敵國和親需曾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吗坚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354