• how tomcat works 五 servlet容器 上


    servlet容器是用来处理请求servlet资源,并为Webclient填充response对象的模块。在上一篇文章(也就是书的第四章)我们设计了SimpleContainer类,让他实现Container接口,也基本完毕了容器的作用。

    可是我们得知道在实际的tomcat中有4类容器:
    Engine: 表示整个Catalina servlet引擎;
    Host: 包括一个或多个Context容器的虚拟主机;
    Context:表示一个Web应用程序,一个Context能够包括多个Wrapper
    Wrapper: 表示一个独立的Servlet;
    UML图例如以下:



    Container容器中包括addChild(Container con),removeChild(Container con),findChild(String name),findChildren()四个方法,方法的作用就不解释了,Wrapper就表示一个独立的Servlet所以,它的addChild方法体为空。


    在本节,我们主要展示Wrapper的特性,其它的容器以后再说。


    首先我们看看时序图(第一次画 可能在时序图规则上有些问题)


    管道任务

    这节主要说明档连接器调用了servlet容器的invoke方法后发生的事情。
    管道包括了一个servlet容器要运行的任务(和本身的servlet无关,是指额外运行的任务)
    一个Wrapper会包括一个管道,而一个管道会包括若干个Valve(阀)
    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 class SimplePipeline implements Pipeline {
    
      public SimplePipeline(Container container) {
        setContainer(container);
      }
    
      // The basic Valve (if any) associated with this Pipeline.
      protected Valve basic = null;
      // The Container with which this Pipeline is associated.
      protected Container container = null;
      // the array of Valves
      protected Valve valves[] = new Valve[0];            //全部的阀
    }


    在阀中,请求会像水一样经过管道中的每个阀,上图中的阀n会调用我们请求的servlet类。
    再调用容器的invoke方法后,会到例如以下的方法
    invokeNext是StandardPipelineValveContext类中的方法(StandardPipelineValveContext是管道的一个内部类,因此能够訪问管道的全部成员)
     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("No valve");
          }
        }
      } // end of inner class


    ok?

    如水般流过。以下这行代码值得我们细致看看

    valves[subscript].invoke(request, response, this);

    也可參阅拙作

    <<说说struts2中拦截器的请求流程一(模拟大致流程)>>
    http://blog.csdn.net/dlf123321/article/details/40078583


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



    这行代码 valveContext.invokeNext(request, response); 看懂了吧
    还有一方面 大家应该能看出来,全部的阀都是逆向运行的,每次运行一个阀的时候,阀都会先传递给后面的阀,等到后面的运行完成后,才运行自己的业务逻辑。


    当通道里的处了最后一个基础阀外,都走了一遍的时候自然就运行例如以下代码
     basic.invoke(request, response, this);
     问题是basic是谁?


    这个咱们等会再说。

    Pipeline接口

    我们在这里使用的是SimplePipeline,它实现了Pipeline的接口。
    public interface Pipeline {
    public Valve getBasic();
    public void setBasic(Valve valve);
    public void addValve(Valve valve);
    public Valve[] getValves();
    public void invoke(Request request, Response response)
    throws IOException, ServletException;
    public void removeValve(Valve valve);
    }
    Pipeline里面有个基础阀,是最后调用的,负责处理request对象鱼response对象。因而这里有getset方法
    通道里面存放的是阀,所以剩下的方法就不用多说了吧。


    另外tomcat4中,还存在一个StandardPipelineValveContext,上面已经说它是SimplePipeline的一个内部类。

    Valve接口

    public interface Valve {
    public String getInfo();
    public void invoke(Request request, Response response,ValveContext context) throws IOException, ServletException;
    }


    非常清晰,不是吗?

    Contained接口

    public interface Contained {
    public Container getContainer();
    public void setContainer(Container container);
    }


    实现这个接口的类,至多与一个servlet容器相关联。

    Wrapper接口

    首先我们在文章開始的时候就说了,Wrapper包装的是一个最基础的servlet
    这个接口里面有两个方法比較重要
    public javax.servlet.Servlet allocate() throws javax.servlet.ServletException;
    public void load() throws javax.servlet.ServletException;
    allocate会分配一个已经初始化的servlet实例,
    load会加载它。


    Wrapper应用程序

    类图例如以下:



    SimpleLoader类

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


    构造函数会初始会类载入器,供SimpleWrapper使用

    SimpleWrapper类

    该类实现了 org.apache.catalina.Wrapper 接口而且实现了 allocate 方法和load 方法。
    器构造函数例如以下
      public SimpleWrapper() {
        pipeline.setBasic(new SimpleWrapperValve());
      }
    上文我们曾问过,basic从哪里来,从这里来!

    SimpleWrapperValve类

    通过pipeline.setBasic(new SimpleWrapperValve())我们知道SimpleWrapperValve类是一个基础阀,用来处理传递Servlet的參数,例如以下:
    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) {
        }
      }


     应该没有什么问题。

    ClientIPLoggerValve类

    是用来显示client的ip地址的,代码从略,另外还有HeaderLoggerValve,用来显示http的请求头,代码从略。


    Bootstrap1启动类

    public final class Bootstrap1 {
      @SuppressWarnings("deprecation")
    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();
        }
      }
    }


    执行结果例如以下:

    看到了,事实上在部分里,无论你请求那个服务,返回的结果都一样,为什么?这个还用我来解释吗?


    这一章东西比較多,关于context容器的使用,我们放在下一节中。


  • 相关阅读:
    6.5 列出当前目录所有文件
    6.4 协程写文件
    6.3 写文件
    6.2 创建空目录
    6.1 os 获取文件状态
    5.13 json
    es2016
    短路原理
    fexbox
    vue @
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/6803715.html
Copyright © 2020-2023  润新知