关于容器之wrapper包装器

本文探讨了Tomcat容器中的wrapper包装器,分析了如何处理servlet请求的过程。从容器加载servlet、引入流水线pipeline及阀门机制到连接器与容器的交互,揭示了Tomcat设计的合理性。通过类比,将请求比作浑水,容器作为净化工厂,阀门作为过滤器,阐述了框架引入容器带来的便利性,如添加日志组件等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于容器之wrapper包装器的思考:

Tomcat引入了连接器connector,容器container,容器的这种思想在很多框架中都能看到,例如struts2里的xwork容器,spring里的容器。Tomcat中的容器包括:

①  Engine:表示整个Catalina的servlet引擎

②  Host:表示一个拥有数个上下文的虚拟主机

③  Context:表示一个web应用,一个context包含一个或多个wrapper

④  Wrapper:表示一个独立的servlet

下面简单介绍只包含wrapper容器的包装器,下图表示一个请求在单个容器wrapper中的流转:


启动服务器后,连接器中的监听就已经蓄势待发,等待客户端的连接,这时客户端发送请求,连接器将请求交给了容器,如果说,我们需要处理一个servlet,容器应该怎么办?

① 容器中要有一个能加载servlet的类加载器

② 容器要知道加载哪个类

③ 容器要引入流水线pipeline

④  为流水线配上阀门

⑤  将连接器和容器连接上

Tomcat为什么要这样设计,大师这样设计肯定是有理由的,而我只能找生活的实例去说服自己,这样做确实是好的。

客户端的请求就像一瓶浑浊不堪的水,里面什么都有,这瓶水需要进行加工最后得到纯净水,连接器就像运输车,时刻待命,要将这些水运送到指定的工厂(容器),工厂拿到材料,为了效率和规范,会有很多条流水线,由于原材料中含有很多杂质,流水线上会有很多阀门,像过滤器一样,每过一个阀门,你都可以进行处理,但是,必须经过所有阀门,当经过最后一个阀门,纯净水也就生产出来了。

所以很多框架引入容器还是很方便的,比如,我想记录一下这些水都是在什么时间被依次处理的(就好比是日志),那么,我们就可以在容器中引入日志组件,让他专门来记录日志……

 

那么,基于上面只包含一个容器即wrapper该如何实现:

下面的代码引自《How TomcatWork》


根据上面的步奏我们先看看启动类:

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

这是启动类的main方法,首先声明连接器,再声明容器,类的加载器,声明阀门,将类的加载器、阀门放入容器,最后连接器启动。那么容器什么时候被调用呢,查看连接器的代码就可发现这段代码:

try {
                ((HttpServletResponse) response).setHeader
                    ("Date", FastHttpDateFormat.getCurrentDate());
                if (ok) {
                    connector.getContainer().invoke(request, response);
                }

connector.getContainer().invoke(request,response);

容器调用invoke方法。

那么wrapper容器的invoke又是什么呢:

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

显然,容器调用了流水线pipeline的invoke方法。

public void invoke(Request request, Response response)
    throws IOException, ServletException {
    // Invoke the first Valve in this pipeline for this request
    (new SimplePipelineValveContext()).invokeNext(request, response);
  }

流水线的invoke方法又调用它内部类SimplePipelineValveContext的invokeNext方法。

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

然后我们看看每个阀门是怎么定义的:

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);也就是下一个阀门的invoke方法,最后当调用最基本的阀门(也就是最后一个阀门)后,容器的处理过程结束。

所以基本阀门是最后被执行的,前面的方法都在栈中,前面两个阀门先入栈,入栈后先调用了invokeNext,下一个阀门入栈,下一个阀门又调用invokeNext,基本阀门入栈,因为他是最后一个阀门,执行完出栈。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值