Tomcat4x5x内部的Container接口和Wrapper容器的学习

作者: admin 分类: Tomcat 发布时间: 2019-11-19 17:24  阅读: 33 views

servlet容器是用来处理请求servlet资源,并为web客户端填充rsponse对象的模块。servletring器是org.apache.catalina.Container接口的示例。在Tomcat中,有4中类型的容器,分别是

  • Engine(表示整个Catalina servlet引擎)
  • Host(表示包含有一个或多个Context容器的虚拟主机)
  • Context(表示一个Web应用程序。一个Context可以有多个Wrapper)
  • Wrapper(表示一个独立的servlet)

这里学习Container接口和Context和Wrapper两种容器。

Container接口

Tomcat中的servlet容器必须实现Container接口。然后将本实例传入到连接器的setContainer()方法中。这里连接器才能调用servlet容器的invoke()方法。

HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);

以上的四个servlet容器都继承了Container接口。类图如下(找不到高清的)
container类图

一个容器可以由0个或多个低层级的子容器。一般情况下,一个Context实例会有一个或者多个wrapper实例,一个Host实例中会有0个或多个Context实例。但是,Wrapper类型处于层级结构的最底层。因此它无法在包含子容器了。。可以通过Container接口的addChild()方法向某容器中添加纵容其。removeChild()方法删除子容器。

容器可以包含一些支持的组件,如载入器、记录器、管理器、领域和资源等。可以通过Container接口的addChild提供了与这些组件关联的方法。同事满足在部署应用时,tomcat管理员可以通过编辑配置文件server.xml来决定使用哪种容器。是通过引入容器中的管道(pipeline)和阀(value)的集合实现的

管道任务

管道包含该servlet容器将要调用的任务。一个阀表示一个具体的执行任务。在servlet容器管道中,有一个基础阀,可以增加任意数量的阀,不包括基础阀。

阀1  阀2 阀3 .... 阀n    基础阀

管道和servlet的过滤器链一样,而阀和过滤器一样,可以处理传递给它的request对象和response对象。当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行。

一个servlet容器可以有一条管道。当调用了容器是invoke()方法后,容器会将处理工作交由管道完成,而管道会调用其中的第一个阀开始处理。直到处理完所有的阀。伪代码如下:

//阀的循环调用
for(int n = 0;n< values.length;n++){
    value[n].invoke(...);    
}
//基础阀的调用
basicValue.invoke(...);

tomcat中,作者提供了 pipeline接口、value接口、valuecontext接口、Contained接口等来实现以上过程。示例是 org.apache.catalina.core.ContainerBase容器类的invoke()实现。
这里并没有直接在容器本身的invoke方法中编写任务处理逻辑,而是调用了pipeline的invoke()方法,该pipeline是实例化了org.apache.catalina.core.StandardPipeline,

protected Pipeline pipeline = new StandardPipeline(this);

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

invoke()方法如下:

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

其中,StandardPipelineValveContext 实现了ValveContext接口,示例如下:
这里是tomcat4的写法,tomcat5移除了StandardPipelineValveContext,换了StandardValueContext累来实现。

protected class StandardPipelineValveContext
    implements ValveContext
  {
    protected int stage = 0;

    protected StandardPipelineValveContext() {}

    public String getInfo()
    {
      return StandardPipeline.this.info;
    }

    public void invokeNext(Request request, Response response)
      throws IOException, ServletException
    {
      int subscript = this.stage;
      this.stage += 1;
      if (subscript < StandardPipeline.this.valves.length) {
        StandardPipeline.this.valves[subscript].invoke(request, response, this);
      } else if ((subscript == StandardPipeline.this.valves.length) && (StandardPipeline.this.basic != null)) {
        StandardPipeline.this.basic.invoke(request, response, this);
      } else {
        throw new ServletException(StandardPipeline.sm.getString("standardPipeline.noValve"));
      }
    }
  }

以上的这种写法,我只是粗浅的认为作者将管道与阀的概念做了隔离及结构上的区分。比硬编码带来的好处是什么,除了结构划分外我领悟不到…. (mark一下)

在阅读源码时,要切记context、wrapper都是servlet容器,是tomcat内置的容器,HttpConnector连接器的setContainer(xx)方法会加载容器的配置结构等。如下:

package ex05.pyrmont.startup;

import ex05.pyrmont.core.SimpleLoader;
import ex05.pyrmont.core.SimpleWrapper;
import ex05.pyrmont.valves.ClientIPLoggerValve;
import ex05.pyrmont.valves.HeaderLoggerValve;
import org.apache.catalina.Loader;
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 Bootstrap1 {
  public static void main(String[] args) {

/* call by using http://localhost:8080/ModernServlet,
   but could be invoked by any name */

    HttpConnector connector = new HttpConnector();
    Wrapper wrapper = new SimpleWrapper();
    wrapper.setServletClass("ModernServlet");
    Loader loader = new SimpleLoader();
    Valve valve1 = new HeaderLoggerValve();
    Valve valve2 = new ClientIPLoggerValve();

    wrapper.setLoader(loader);
    ((Pipeline) wrapper).addValve(valve1);
    ((Pipeline) wrapper).addValve(valve2);

    connector.setContainer(wrapper);

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

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

Wrapper容器

这里的warpper容器将通过Loader接口的实例 SimpleLoader来完成servlet容器的载入,如下

SimpleLoader.java


public class SimpleLoader implements Loader { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; ClassLoader classLoader = null; Container container = null; public SimpleLoader() { try { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(WEB_ROOT); String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ; urls[0] = new URL(null, repository, streamHandler); classLoader = new URLClassLoader(urls); } catch (IOException e) { System.out.println(e.toString() ); } } public ClassLoader getClassLoader() { return classLoader; } .... }

SimpleWrapper.java


public class SimpleWrapper implements Wrapper, Pipeline { // the servlet instance private Servlet instance = null; private String servletClass; private Loader loader; private String name; private SimplePipeline pipeline = new SimplePipeline(this); protected Container parent = null; public SimpleWrapper() { pipeline.setBasic(new SimpleWrapperValve()); } public synchronized void addValve(Valve valve) { pipeline.addValve(valve); } public Servlet allocate() throws ServletException { // Load and initialize our instance if necessary if (instance==null) { try { instance = loadServlet(); } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException("Cannot allocate a servlet instance", e); } } return instance; } private Servlet loadServlet() throws ServletException { if (instance!=null) return instance; Servlet servlet = null; String actualClass = servletClass; if (actualClass == null) { throw new ServletException("servlet class has not been specified"); } Loader loader = getLoader(); // Acquire an instance of the class loader to be used if (loader==null) { throw new ServletException("No loader."); } ClassLoader classLoader = loader.getClassLoader(); // Load the specified servlet class from the appropriate class loader Class classClass = null; try { if (classLoader!=null) { classClass = classLoader.loadClass(actualClass); } } catch (ClassNotFoundException e) { throw new ServletException("Servlet class not found"); } // Instantiate and initialize an instance of the servlet class itself try { servlet = (Servlet) classClass.newInstance(); } catch (Throwable e) { throw new ServletException("Failed to instantiate servlet"); } // Call the initialization method of this servlet try { servlet.init(null); } catch (Throwable f) { throw new ServletException("Failed initialize servlet."); } return servlet; } public String getInfo() { return null; } public Loader getLoader() { if (loader != null) return (loader); if (parent != null) return (parent.getLoader()); return (null); } .... }

SimpleWrapper类有一个pipeline实例,并设置了基础阀,都是在其构造函数中完成。
SimpleWrapperValue是一个基础阀,专门用于处理SimpleWrapper类的请求。实现如下


public class SimpleWrapperValve implements Valve, Contained { protected Container container; public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { SimpleWrapper wrapper = (SimpleWrapper) getContainer(); ServletRequest sreq = request.getRequest(); ServletResponse sres = response.getResponse(); Servlet servlet = null; HttpServletRequest hreq = null; if (sreq instanceof HttpServletRequest) hreq = (HttpServletRequest) sreq; HttpServletResponse hres = null; if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres; // Allocate a servlet instance to process this request try { servlet = wrapper.allocate(); if (hres!=null && hreq!=null) { servlet.service(hreq, hres); } else { servlet.service(sreq, sres); } } catch (ServletException e) { } } public String getInfo() { return null; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }

要注意其中实例的调用关系。servlet.service(hreq, hres)中的servlet是SimpleWrapperValve对象本身,而不是SimpleWrapper。

Context容器

表示一个web应用程序,其中可以包含一个或者多个Wrapper实例作为其子容器。当程序中有多个Wrapper实例时,需要使用一个映射器(组件),帮助servlet容器处理某个指定的请求。(只在tomcat4中,之后已经换了方式)

这里是映射器的实现示例SimpleContextMapper.java

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

这里是一个context加载多个Wrapper时的示例

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();
    //servlet1容器
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    //servlet2容器
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

    //web应用程序容器
    Context context = new SimpleContext();
    context.addChild(wrapper1);
    context.addChild(wrapper2);

    //管道中的阀1、阀2
    Valve valve1 = new HeaderLoggerValve();
    Valve valve2 = new ClientIPLoggerValve();

    //管道中添加阀
    ((Pipeline) context).addValve(valve1);
    ((Pipeline) context).addValve(valve2);

    //当有多个wrapper是,使用映射器
    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();
    }
  }
}

注: 到这里代码量和增加的设计思路已经比较多了,要结合源码 + 代码调试比较容易理解。


   原创文章,转载请标明本文链接: Tomcat4x5x内部的Container接口和Wrapper容器的学习

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读