《How Tomcat Works》通译(3)之Context容器

《How Tomcat Works》翻译(3)之Context容器

一、The Context应用

    在这章的第一个应用程序(Wrapper容器),你已经学会了怎样部署一个仅仅只有一个wrapper容器的简单Web应用程序。这个程序只有一个servlet。尽管在一些应用程序中可能只需要仅仅一个servelt,但是大部分应用程序是需要多个servlet.因此在本应用程序中有增加了一个类型的容器(Context容器,它是Wrapper的父类容器)。

 

   本应用程序阐明了怎样用一个Context容器(该容器包含两个Wrapper容器即两个servlet类)。因此,你拥有了更多的Wrapper容器,就需要借助一个组件(Mapper),帮助容器(Context容器)选择一个要处理特定请求的Request的孩子容器(Wrapper容器)。

 

         注意:A Mapper组件出现在Tomcat4中,但是在Tomcat5中该组件移除了,引用了另外一种方式来查找孩子容器(Wrapper容器)。

       在本应用程序中,你的Mapper实例是ex05.pyrmont.core.SimpleContextMapper类,它实现了org.apache.catalina.Mapper接口(这是在Tomcat4)。一个容器也能够用多个mappers(为了支持不同种类的协议)。在本应用程序中一个mapper支持一个request协议。比如,一个容器可能一个mapper对应Http协议,而另外一个mapper对应了the Https协议。下面代码是Tomcat4提供的Mapper接口:

 

package org.apache.catalina;

public interface Mapper {
    public Container getContainer();
    public void setContainer(Container container);
    public String getProtocol();
    public void setProtocol(String protocol);
    public Container map(Request request, boolean update);


}

 The getContainer方法返回就是容器与mapper相关联,The setContainer方法就是设置the mapper与 the Container相关联。The getProtocol方法返回the mapper负责处理的协议,setProtocol方法就是把协议的名称赋给负责处理协议的the mapper.The map方法返回一个将要处理特定请求的孩子容器。

         下面是本应用程序的类图:


《How Tomcat Works》通译(3)之Context容器
    注解:一定要把类图和下面的一段看明白,才能够真正的理解父容器和孩子容器之间的关系和设计者的思路,这个十分重要。他们是借助the mapper组件来进行联系。。。

     The SimpleContext代表一个Context容器,The SimpleContextMapper作为mapper组件,the SimpleContextValve作为一个basic valve.两个Valve分别是:ClientIPLogger、HeaderLoggerValve全部添加到The Context容器。两个Wrapper(都是由SimpleWrapper组成)被添加到The COntext容器里(就是作为它的孩子容器)。The wrappers使用SimpleWrapperValve作为他们的basic valve但是没有添加其他的valves.

      The Context 应用程序使用了上个应用程序一样的的Loader和两个Valves。然而,The Loader和Valves是与the Context容器相关联而不是与Wrapper容器相关。注解:理解这里十分重要。因此

the Loader能够被两个Wrapper容器公用。The Context被赋给了The connector。因此,只要The Connector接收到一个Http请求,它就会调用the Context的invoke方法。如果你已经对上个程序已经领悟到了其中的精髓,那么剩下的调用关系就很容易理解了。

      1、一个Container要有一个pipeline.The Container的Invoke方法就会调用the pipeline的Invoke方法。

      2、The pipeline的Invoke方法就会调用添加整个Container的所有Valve中的Invoke方法。

      3、在一个Wrapper容器里,The basic valve负责加载与之关联的servlet的类以及相应The Request请求。

      4、在拥有孩子容器的Context,The basic Valve使用mapper组件目的就是找出负责处理The request请求的孩子容  器。如果能够找到孩子容器,那么the Context 容器就会调用孩子容器的the invoke方法。然后有返回到第一步。

   注解:这四个过程十分的重要,一定充分的了解。不理解的时候要反复的看,或者就是跟踪源码,这种设计思想非常值得学习。

    现在让我们看看实现处理的顺序。

  The SimpleContext类中的Invoke方法调用了the pipeline的invoke方法

 

     public void invoke(Request request,Response response)

         throws IOException,ServletException{

         pipeline.invoke(request,response);

    }

   The pipeline是有the SimplePipeline类代表,他的invoke方法调用方式如下面的代码:

 

/***
	 * 该方法交给内部类中的invokeNext(Request,Response)处理
	 */
	public void invoke(Request request, Response response) throws IOException,
			ServletException {
		(new SimplePipelineValveContext()).invokeNext(request, response);
	}

 这个在“Pipelining Task”部分已经解释了,这里就不解释。这个代码调用了所有添加在子容器的(Wrapper)的Valves的the invoke方法然后又调用了the basic valve的Invoke方法。在 SimpleContext容器中,The SimpleContextValve类代表the basic Valve。在调用这个基本阀门中的invoke方法时,the SimpleContextValve使用了the context 中的组件the mapper 查找一个wrapper:

       Wrapper wrapper=null;

       try{

                wrapper=(Wrapper)context.map(request,true);

 

         }

  如果the Wrapper找到了,就会调用The Wrapper的invoke方法:

      wrapper.invoke(request,response);

 

   在本应用程序中一个Wrapper是由the SimpleWrapper类代表的。下面SimpleWrapper中的the invoke方法代码与the SimpleContext类的invoke方法的代码是一样的.

 

 

     public void invoke(Request request,Response response)

         throws IOException,ServletException{

         pipeline.invoke(request,response);

    }

 

The pipeline是The SimplePipeline的实例(他的invoke方法已经在上面讨论了)。特别注意的是,本应用程序的The Wrappers没有the valves除了有the basic valve(它由The SimpleWrapperValve类代表)。The wrapper的pipeline调用了the SimpleWrapperValve类的invoke方法(主要是分配一个servlet以及调用该servlet的service方法)详细解释已经在“The Wrapper应用程序”解说了。

 

     还需要注意的是the wrapper容器是没有和a loader关联,而是与the context 容器关联的。因此the SimpleWrapper的getLoader方法返回父类中的loader.

 

     这里有四个类(SimpleContext,SimpleContextValve,SimpleContextMapper,Bootstrap2)在第一个应用程序没有解释,下面将会详细讨论这四个类。

 

 二、ex05.pyrmont.core.SimpleContextValve

       这个类代表了the SimpleContext容器的the basic valve,他的the invoke方法是十分关键的,可以看下面代码:

 

  public void invoke(Request request, Response response, ValveContext valveContext)
    throws IOException, ServletException {
    // Validate the request and response object types
    if (!(request.getRequest() instanceof HttpServletRequest) ||
      !(response.getResponse() instanceof HttpServletResponse)) {
      return;     // NOTE - Not much else we can do generically
    }

    // Disallow any direct access to resources under WEB-INF or META-INF
    HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
    String contextPath = hreq.getContextPath();
    String requestURI = ((HttpRequest) request).getDecodedRequestURI();
    String relativeURI =
      requestURI.substring(contextPath.length()).toUpperCase();

    Context context = (Context) getContainer();
    // Select the Wrapper to be used for this Request
    Wrapper wrapper = null;
    try {
      wrapper = (Wrapper) context.map(request, true);
    }
    catch (IllegalArgumentException e) {
      badRequest(requestURI, (HttpServletResponse) response.getResponse());
      return;
    }
    if (wrapper == null) {
      notFound(requestURI, (HttpServletResponse) response.getResponse());
      return;
    }
    // Ask this Wrapper to process this Request
    response.setContext(context);
    wrapper.invoke(request, response);
  }

 三、ex05.pyrmont.core.SimpleContextMapper

   The SimpleContextMapper类(它实现了org.apache.catalina.Mapper接口注意这是在Tomcat4)主要设计的思想就是与SimpleContext实例相关联。

package ex05.pyrmont.core;

import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Container;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.Mapper;
import org.apache.catalina.Request;
import org.apache.catalina.Wrapper;

public class SimpleContextMapper implements Mapper {

  /**
   * The Container with which this Mapper is associated.
   */
  private SimpleContext context = null;

  public Container getContainer() {
    return (context);
  }

  public void setContainer(Container container) {
    if (!(container instanceof SimpleContext))
      throw new IllegalArgumentException
        ("Illegal type of container");
    context = (SimpleContext) container;
  }

  public String getProtocol() {
    return null;
  }

  public void setProtocol(String protocol) {
  }


  /**
   * Return the child Container that should be used to process this Request,
   * based upon its characteristics.  If no such child Container can be
   * identified, return <code>null</code> instead.
   *
   * @param request Request being processed
   * @param update Update the Request to reflect the mapping selection?
   *
   * @exception IllegalArgumentException if the relative portion of the
   *  path cannot be URL decoded
   */
  public Container map(Request request, boolean update) {
    // Identify the context-relative URI to be mapped
    String contextPath =
      ((HttpServletRequest) request.getRequest()).getContextPath();
    String requestURI = ((HttpRequest) request).getDecodedRequestURI();
    String relativeURI = requestURI.substring(contextPath.length());
    // Apply the standard request URI mapping rules from the specification
    Wrapper wrapper = null;
    String servletPath = relativeURI;
    String pathInfo = null;
    String name = context.findServletMapping(relativeURI);
    if (name != null)
      wrapper = (Wrapper) context.findChild(name);
    return (wrapper);
  }
}

 如果你传过来的The Container不是the SimpleContext实例,那么The setContainer方法就会抛出一个 IllegalAgumentException异常。the map 方法返回了一个孩子容器(A wrapper只要负责处理the Request请求)。The Map 方法需要传两个参数(a request对象和一个布尔值)。这个实现忽略了第二个参数。该方法主要任务就是检索来自the request 对象的the context路径,然后使用了the Context的findServletMapping方法获得与路径相关联的名字。如果名字找到了,那么就是用the Context中的findChild方法获得一个Wrapper实例。

 

 

 四、ex05.prymont.core.SimpleContext

    在本应用程序中The SimpleContext类实现了The context接口。它是主要的容器,并且把自己赋给了the Connector.然而,在处理每个servlet是由a wrapper容器负责。本应用程序有两个servlet(PrimitiveServlet,ModernServlet),因此就需要两个Wrapper,他们的名字分别为Primitive、Modern。对于The SimpleContext决定每一次The request请求需要哪个wrapper的条件是:一定要the request URL 模式要与the wrapper的名字相匹配。在本应用程序中,我们有两个URL模式分别是用来调用两个wrapper的先决条件。第一个模式是/Primitive,它与the wrapper Primitive相匹配。第二个模式是/Modern,它与The Wrapper Modern相匹配。当然,你能够使用更多的模式给一个确定的servlet。你仅仅需要添加这些模式。

           The SimpleContext类实现了The Container和Context接口,所有存在许多方法。但是大部分方法是空实现的方法,然而与mapping相关的方法才是真正的代码(也就是本应用程序所需要的代码),这些方法可以看下面的描述.

 

       1、addServletMapping    添加一个URL pattern/wrapper匹配对。你添加的每个模式对是要调用哪个wrapper的一个先决条件。即The Context容器会根据模式匹配对来决断用哪个Wrapper容器(他的孩子容器)

 

        2、findServletMapping  通过一个URL模式获得the Wrapper的名字。这个方法就是根据URL模式找出要调用哪个Wrapper。如果用户用的URL模式没有在addServleyMapping方法添加,那么使用此方法返回null.

 

        3、 addMapper    添加一个组件mapper到the Context容器中。The SimpleContext类拥有the mapper和mappers两个成员变量。mapper是一个默认的mapper组件,mappers包含了The SimpleContext实例所有的mapper

。The first mapper添加到了这个SimpleContext中变成了一个默认的mapper组件。

 

         4、findMapper     找出一个正确的mapper组件。在 SimpleContext类中该方法是返回一个默认的组件mapper.

 

          5、map 返回一个负责处理的request请求的wrapper容器

 

     除此之外,SimpleContext类也实现了addChild,findChild,findChildren方法。The addChild方法就是把一个wrapper添加到the context中,the findChild方法通过一个具体的名字找出一个wrapper,findChildren方法是返回在SimpleContext实例中的所有的wrapper容器即它的所有孩子容器。

 

 四、ex05.pyrmont.startup.Bootstrap2

    The Bootstrap2类是一个启动本应用程序,这个类与BootStrap1功能代码十分相似,下面是其代码:

 

package ex05.pyrmont.startup;

import ex05.pyrmont.core.SimpleContext;
import ex05.pyrmont.core.SimpleContextMapper;
import ex05.pyrmont.core.SimpleLoader;
import ex05.pyrmont.core.SimpleWrapper;
import ex05.pyrmont.valves.ClientIPLoggerValve;
import ex05.pyrmont.valves.HeaderLoggerValve;
import org.apache.catalina.Context;
import org.apache.catalina.Loader;
import org.apache.catalina.Mapper;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;

public final class Bootstrap2 {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

    Context context = new SimpleContext();
    context.addChild(wrapper1);
    context.addChild(wrapper2);

    Valve valve1 = new HeaderLoggerValve();
    Valve valve2 = new ClientIPLoggerValve();

    ((Pipeline) context).addValve(valve1);
    ((Pipeline) context).addValve(valve2);

    Mapper mapper = new SimpleContextMapper();
    mapper.setProtocol("http");
    context.addMapper(mapper);
    Loader loader = new SimpleLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    connector.setContainer(context);
    try {
      connector.initialize();
      connector.start();

      // make the application wait until we press a key.
      System.in.read();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 主方法开始实例化Tomcat 默认的连接器,两个wrappers,wrapper1,wrapper2.这些Wrappers的确定的名字是Primitive和Modern,而Primitiv,Modern对应的Servlet类分别是PrimitiveServlet,ModernServlet

 

 

 HttpConnector connector = new HttpConnector();
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

 然后,主方法创建了一个SimpleContext实例,SimpleContext容器又添加了wrapper1,wrapper2作为孩子容器。也实例化两个valve:ClientIPLogger,HeaderLoggerValve并且把两个Valve添加到SimpleContext容器中。

 

    Context context = new SimpleContext();
    context.addChild(wrapper1);
    context.addChild(wrapper2);

    Valve valve1 = new HeaderLoggerValve();
    Valve valve2 = new ClientIPLoggerValve();

    ((Pipeline) context).addValve(valve1);
    ((Pipeline) context).addValve(valve2);

 接下来,主方法就构造一个mapper对象(The SimpleMapper类),并把该组件添加到SimpleContext容器中。这个mapper组件主要负责找出在Context容器中的孩子容器(专门处理Http请求)

 

 

    Mapper mapper = new SimpleContextMapper();
    mapper.setProtocol("http");
    context.addMapper(mapper);

 对于加载一个servlet你就需要一个Loader.在这里你使用了SimpleLoader类,正如第一个应用程序那样。然而,The Loader 添加到The Context 容器中,这样就可以为两个wrappers共同使用这个加载器了。The Wrappers将使用getLoader找出the loader,因为the Context是the Wrapper的父容器。

Loader loader = new SimpleLoader();
    context.setLoader(loader);
 

现在,该是时候添加servlet的匹配模式了。你要添加两个模式给这两个wrappers

 

 

    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");

 最后,要把the Context容器传给the connector,然后启动the connector

 

 connector.setContainer(context);
    try {
      connector.initialize();
      connector.start();

      // make the application wait until we press a key.
      System.in.read();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

 五、运行这个应用程序

     这里翻译我就不详细说了

 

 六、总结

     介绍完the connector后,The Container是第二个主要的模块。The Container使用许多的其他模块,如:Loader,Logger,Manager等等。这里也有四种类型的容器:Engine,Host,Context,Wrapper.一个Catalina部署是不会把四个容器全部的展现出来。在这章中的两个应用表明了一个部署可以有单个的wrapper容器或者是由多个Wrapper容器组成的Context容器。

1 楼 aixuebo 2010-12-07  
给点鼓励,翻译的不错。。。我曾经也翻译过,但是没有分享出来,原因就是自己太懒了,挺佩服你的精神的。以后向你学习。
2 楼 乌索普 2011-10-25  
非常鼓励,不过当看到孩子容器的时候,我有点吃不消,
3 楼 he_wen 2011-10-29  
看不懂自己下载源代码 单步调试跟踪代码 可以介入eclipse插件relo 转换成类结构分析源代码