天天看点

tomcat源码分析-http请求在Container中的执行路线

  在coyoteadapter的service方法中,主要干了2件事:

    1. org.apache.coyote.request -> org.apache.catalina.connector.request extends httpservletrequest

        org.apache.coyote.response -> org.apache.catalina.connector. response extends httpservletresponse

        context和wrapper定位

    2. 将请求交给standardenginevalue处理

tomcat源码分析-http请求在Container中的执行路线

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源码分析-http请求在Container中的执行路线

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()方法中,

tomcat源码分析-http请求在Container中的执行路线

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。

    另外,由上面的代码可知,在随后的请求路线中,engine可有connector获取,context和wrapper可直接由request获取,host也可由request获取。

tomcat源码分析-http请求在Container中的执行路线

public host gethost() { return ((host) mappingdata.host); }  

    上面的代码中还涉及到了两个很重要的概念--pipeline和value,我们不妨先一睹container的调用链和时序图。

tomcat源码分析-http请求在Container中的执行路线
tomcat源码分析-http请求在Container中的执行路线

     对于每个引入的http请求,连接器都会调用与其关联的servlet容器的invoke方法。然后,servlet容器会调用其所有子容器的invoke方法。为什么必须要有一个host容器呢?

    在tomcat的实际部署中,若一个context实例使用contextconfig对象进行设置,就必须使用一个host对象,原因如下:

    使用contextconfig对象需要知道应用程序web.xml文件的位置,在其webconfig()方法中会解析web.xml文件

tomcat源码分析-http请求在Container中的执行路线

// parse context level web.xml  

inputsource contextwebxml = getcontextwebxmlsource();  

parsewebxml(contextwebxml, webxml, false);  

在getcontextwebxmlsource方法里

tomcat源码分析-http请求在Container中的执行路线

// servletcontext即core包下的applicationcontext  

url = servletcontext.getresource(constants.applicationwebxml);  

在getresource方法里

tomcat源码分析-http请求在Container中的执行路线

string hostname = context.getparent().getname();  

 因此,除非你自己实现一个contextconfig类,否则,你必须使用一个host容器。

    管道(pipeline)包含该servlet容器将要调用的任务。一个阀(value)表示一个具体的执行任务。在servlet容器的管道中,有一个基础阀,但是,可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即不包括基础阀。有意思的是,可以通过server.xml来动态添加阀。

    管道和阀的工作机制类似于servlet编程中的过滤器链和过滤器,tomcat的设计者采用的是链表数据结构来实现的链条机制,引入了一个类叫valuecontext。值得注意的是,基础阀总是最后执行。

    请求最终会被引导到standardwrapper,本人也是首先从wrapper这一层来入手container的,直接看standardwrappervalue的invoke方法

tomcat源码分析-http请求在Container中的执行路线

@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);  

上面代码中最重要的三处逻辑就是servlet实例的获取与卸载和filter链调用。我们先看卸载servlet实例的代码

tomcat源码分析-http请求在Container中的执行路线

    public void deallocate(servlet servlet) throws servletexception {  

        // unlock and free this instance  

        synchronized (instancepool) {  

            countallocated.decrementandget();  

            instancepool.push(servlet);  

            instancepool.notify();  

我们不考虑singlethreadmodel模型,因为较新版本的tomcat已经不用这种模型了(只有很老的版本才用),显然,通过上面的代码可以知道,常用的是线程池模型。下面给出加载servlet实例的代码

tomcat源码分析-http请求在Container中的执行路线

public servlet allocate() throws servletexception {  

      instance = loadservlet();  

      initservlet(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 (throwable e) {  

                    }  

                } else {  

                        instancepool.wait();  

                    } catch (interruptedexception e) {  

                        // ignore  

                }  

            }  

            countallocated.incrementandget();  

            return instancepool.pop();  

最后,我们来看看filterchain的执行,

tomcat源码分析-http请求在Container中的执行路线

    public void dofilter(servletrequest request, servletresponse response)  

        throws ioexception, servletexception {  

    internaldofilter(request,response);  

private void internaldofilter(servletrequest request,   

                                  servletresponse response)  

    // 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,这可谓是责任链设计模式的一种经典实现。

    好了,现在你可以把前文中connector执行过程和本文的container执行过程结合起来了。我始终相信,深入一点,你会更快乐。

原文链接:[http://wely.iteye.com/blog/2295240]