天天看點

how tomcat works 五 servlet容器 上

servlet容器是用來處理請求servlet資源,并為Web用戶端填充response對象的子產品。在上一篇文章(也就是書的第四章)我們設計了SimpleContainer類,讓他實作Container接口,也基本完成了容器的作用。但是我們得知道在實際的tomcat中有4類容器:

Engine: 表示整個Catalina servlet引擎;

Host: 包含一個或多個Context容器的虛拟主機;

Context:表示一個Web應用程式,一個Context可以包含多個Wrapper

Wrapper: 表示一個獨立的Servlet;

UML圖如下:

how tomcat works 五 servlet容器 上

Container容器中包含addChild(Container con),removeChild(Container con),findChild(String name),findChildren()四個方法,方法的作用就不解釋了,Wrapper就表示一個獨立的Servlet是以,它的addChild方法體為空。

在本節,我們主要展示Wrapper的特性,其他的容器以後再說。

首先我們看看時序圖(第一次畫 可能在時序圖規則上有些問題)

how tomcat works 五 servlet容器 上

管道任務

這節主要說明檔連接配接器調用了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];            //所有的閥
}      
how tomcat works 五 servlet容器 上

在閥中,請求會像水一樣經過管道中的每一個閥,上圖中的閥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中攔截器的請求流程一(模拟大緻流程)>>

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應用程式

類圖如下:

how tomcat works 五 servlet容器 上

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類

是用來顯示用戶端的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();
    }
  }
}      

運作結果如下:

how tomcat works 五 servlet容器 上

看到了,其實在部分裡,不管你請求那個服務,傳回的結果都一樣,為什麼?這個還用我來解釋嗎?