《How Tomcat Works》第五章:容器

上一章讲完了复杂的默认连接器,这一章介绍的是容器。容器呢,是用来处理servlet请求并填充返回对象给web客户端的模块。接口Container定义了容器的形式,容器分为这四种:

  • Engine:表示整个Catalina的serlvet引擎
  • Host:表示一个拥有数个上下文(Context)的虚拟主机
  • Context:表示一个Web应用,包含多个包装器(Wrapper)
  • Wrapper:表示一个独立的serlvet

本章先讲的是Context和Wrapper,不过在讲这两个之前要先了解一下管道(pipeline)和阀门(valve),一个管道(pipeline)中包含了该容器要调用所有的任务,而每个阀门(valve)则代表了每个特定的任务。打个比方,一个饮料厂里有一条生产饮料的流水线,那条流水线的管道(pipeline)刚开始流过的是自来水,后来经过一个一个的阀门(valve),每个阀门都添加需要添加的添加剂(特定的任务),最后从出口流出来的就是按照配方(配置文件server.xml)做出来的饮料了。

一个容器可以有一个管道。当容器的invoke()方法被调用时,容器将通过管道调用第一个阀门,随后一个接着一个调用阀门,直到所有的阀门被调用完为止。那么这些是怎么实现的呢?首先我们来看一张类图:
这里写图片描述
然后,我们来看一下最重要的invoke方法,有很多接口都有invoke方法,我们都来看一下:

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

首先Container接口有invoke方法,这个方法在containerBase得到了实现:

 public void invoke(Request request, Response response)
        throws IOException, ServletException {
        pipeline.invoke(request, response);
    }

这里调用了管道的invoke方法,然后我们来看一下实现了ContainerBase的StandarPipeline类,这个类最为关键:

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

        // Invoke the first Valve in this pipeline for this request
        (new StandardPipelineValveContext()).invokeNext(request, response);

    }

protected class StandardPipelineValveContext
        implements ValveContext {
...
        protected int stage = 0;
        public void invokeNext(Request request, Response response)
            throws IOException, ServletException {

            int subscript = stage;
            stage = stage + 1;

            // Invoke the requested Valve for the current request thread
            if (subscript < valves.length) {
                valves[subscript].invoke(request, response, this);
            } else if ((subscript == valves.length) && (basic != null)) {
                basic.invoke(request, response, this);
            } else {
                throw new ServletException
                    (sm.getString("standardPipeline.noValve"));
            }

        }


    }

这里的invoke方法,调用了内部类StandardPipelineValveContext的invokeNext方法,方法中,用stage和subscript 记录了要唤醒的阀门,每次invokeNext方法被调用时,stage会加1,指向下一个要被调用的阀门。所以每个阀门里都会调用invokeNext,用来激活下一个阀门,最后激活基本阀门。

public class HeaderLoggerValve implements Valve, Contained {

  protected Container container;

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

    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);

    System.out.println("Header Logger Valve");
    ServletRequest sreq = request.getRequest();
    if (sreq instanceof HttpServletRequest) {
      HttpServletRequest hreq = (HttpServletRequest) sreq;
      Enumeration headerNames = hreq.getHeaderNames();
      while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement().toString();
        String headerValue = hreq.getHeader(headerName);
        System.out.println(headerName + ":" + headerValue);
      }

    }
    else
      System.out.println("Not an HTTP Request");

    System.out.println("------------------------------------");
  }
...
}

最后来总结一下所有阀门被一个个调用的步骤:

  1. 首先容器调用invoke方法。
  2. 容器的invoke方法会调用管道的invoke方法。
  3. 管道的invoke方法会调用内部类的invokeNext方法,成员属性stage会加一,指向下一个阀门。
  4. invokeNext会调用阀门的invoke方法,做阀门特定的任务。
  5. 阀门会调用invokeNext方法,继续唤醒下一个阀门,直到基本阀门为止。

猜你喜欢

转载自blog.csdn.net/qq_19894073/article/details/80967752