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

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

Tomcat請(qǐng)求響應(yīng)處理(一)的最后,我們看到一個(gè)很長(zhǎng)的鏈?zhǔn)秸{(diào)用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),我們一點(diǎn)點(diǎn)來(lái)分析一下。connector.getService()得到Connector的父容器StandardService,由于StandardService是連接Tomcat兩大組件的橋梁,自然getContainer()又可以得到Container頂層容器StandardEngine,在Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)中說(shuō)過(guò)每個(gè)Container都存在一個(gè)StandardPipeline管道,每個(gè)管道中存在一個(gè)或者多個(gè)Valve閥門。當(dāng)請(qǐng)求來(lái)時(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ì)象為空自然有問(wèn)題,將錯(cuò)誤碼塞入response中返回,最后責(zé)任鏈模式再次出現(xiàn),調(diào)用StandardHost中管道的第一個(gè)閥門,默認(rèn)情況下在server.xml中存在一個(gè)Valve,對(duì)應(yīng)的實(shí)體為AccessLogValve,主要用來(lái)記錄該虛擬主機(jī)的訪問(wèn)情況
圖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門是用來(lái)記錄處理請(qǐng)求中產(chǎn)生錯(cuò)誤的,而上面說(shuō)過(guò),當(dāng)流程中發(fā)生錯(cuò)誤會(huì)存在一個(gè)對(duì)應(yīng)的錯(cuò)誤碼,而該錯(cuò)誤碼又封裝在response中,那這里就不難理解為什么要在調(diào)用鏈返回過(guò)程中再做處理。下一個(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)行了過(guò)濾,記得我剛學(xué)web編程時(shí)老師讓我們把自定義的Servlet放在/WEB-INF下,說(shuō)是不讓直接訪問(wèn)只能內(nèi)部跳轉(zhuǎn)從而保證安全,那不能訪問(wè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ò)濾器有關(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è)過(guò)時(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è)無(wú)參的init(),當(dāng)我們創(chuàng)建Servlet時(shí)可以覆寫該方法,從而在第一次調(diào)用Servlet時(shí)進(jìn)行一些初始化的操作
我們回到圖6中的第二個(gè)紅框,代碼使用工廠創(chuàng)建了一個(gè)過(guò)濾器鏈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)建出過(guò)濾器鏈并將請(qǐng)求與之對(duì)應(yīng)的Servlet與之關(guān)聯(lián),通過(guò)StandardContext得到web.xml中配置的所有<filter-mapping>對(duì)應(yīng)的實(shí)體FilterMap數(shù)組,該數(shù)組中的數(shù)據(jù)來(lái)源逆序調(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è)過(guò)濾器都存在一個(gè)類型為DispatcherType的調(diào)度類型dispatcher,表示該過(guò)濾器對(duì)哪一種請(qǐng)求類型進(jìn)行攔截,共有FORWORDINCLUDEREQUESTASYNCERROR五種類型,默認(rèn)為REQUEST
標(biāo)注(3)遍歷所有的過(guò)濾器數(shù)組,進(jìn)行兩輪匹配判斷,第一輪matchDispatcher(FilterMap, DispatcherType)判斷是否filter配置的調(diào)度類型在上述五種類型之中;第二輪matchFiltersURL(FilterMap, String)判斷請(qǐng)求URL是否命中一個(gè)filter,如若存在一個(gè)匹配過(guò)濾器,那么根據(jù)對(duì)應(yīng)filter名稱從StandardContext中的成員變量HashMap<String, ApplicationFilterConfig> filterConfigs中得到對(duì)應(yīng)的實(shí)例ApplicationFilterConfig。之前的文章曾經(jīng)分析過(guò)在解析web.xml時(shí),過(guò)濾器對(duì)應(yīng)的對(duì)象為FilterDef,但在StandardContext啟動(dòng)時(shí)中間有一步是啟動(dòng)所有的過(guò)濾器,此時(shí)會(huì)將所有的FilterDef轉(zhuǎn)成ApplicationFilterConfig放入該Map
要理解標(biāo)注(4)必須先回憶一下<filter-mapping>配置的方式,要攔截請(qǐng)求其實(shí)有兩種方式:1.配置<url-pattern>過(guò)濾請(qǐng)求路徑;2.配置<servlet-name>過(guò)濾特定Servlet,那代碼中的兩個(gè)for循環(huán)就對(duì)應(yīng)兩種方式了。最后將請(qǐng)求路徑或者請(qǐng)求對(duì)應(yīng)Servlet的過(guò)濾器通過(guò)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);
        }
    }

}

雖然代碼比較長(zhǎng)但是結(jié)構(gòu)還是很清晰的,主要分為上下兩部分。第一部分由整個(gè)if塊包裹,pos是當(dāng)前遍歷ApplicationFilter元素對(duì)應(yīng)的數(shù)組下標(biāo),n為整個(gè)數(shù)組長(zhǎng)度,總的意思就是遍歷過(guò)濾器數(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)處理的過(guò)程分析完畢

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評(píng)論 3 421
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,935評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,892評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,322評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,800評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,084評(píng)論 2 375