Servlet容器是用来处理Servlet资源的。共有四种容器:

  1. Engine(表示整个Catalina servlet引擎);
  2. Host(表示包含一个或多个Context容器的虚拟主机);
  3. Context(表示一个web应用,可包含多个wrapper);
  4. Wrapper(表示一个独立的servlet)。
      四种容器虽然是层层包含的关系,但他们的结构是及其类似的,都继承自Container接口.
      他们的另一个最大相同点就是通过引入容器中的管道(pipeline)和阀(valve)的集合实现的,来看看什么事管道任务
管道任务

  管道包含了此Servlet容器将要调用的任务。一个阀代表一个具体的执行任务。**在Servlet容器中,必包含一个基础阀。**以及N(N>=0)个额外添加的阀,servlet阀的数量就是N(即不包括基础阀)。

graph LR
阀A-->阀B
阀B-->等等等
等等等-->阀C

  之所以说是管道任务,因为阀会一个一个一次被调用执行,基础阀总是最后一个执行,且一定被执行。
  一个servlet容器可以有一条管道,当调用容器的invoke方法后(在连接器中讲过,就是connector.getContainer().invoke(request, response);), 容器就会将处理工作交由管道完成,伪代码如下:

// invoke each valve added to the pipeline
for(int i = 0; i < valves.length; i++) {
    valve[i].invoke(...);
}

// then invoke the basic valve
basicValve.invoke(...);

在ContainerBase抽象类中,有如下方法,是几个容器共同使用的:

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

pipeline对象就是所说的管道,来看看StandardPipeline(默认都是使用这个管道类)的invoke方法

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

protected class StandardPipelineValveContext
    implements ValveContext {

    protected int stage = 0;
    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
                (sm.getString("standardPipeline.noValve"));
        }
    }
}

  它会新建一个ValveContext接口的实例并调用其invokeNext()方法,valves[subscript].invoke(request, response, this);会将自身传给valve对象,valve在执行invoke后需要再次调用其invokeNext(),顺序把阀执行下去。
  后续的Tomcat版会替换掉这个内部类,改用链表结构来实现阀的依次执行.

Wrapper

  前面提到wrapper是servlet容器的最小单位,而且wrapper负责管理基础servelt类的servelt生命周期,即调用servelt.init()、servlet.destory()等方法。
代码示例

Context

  一个Context可包含一个及多个Wrapper.
代码示例

Host

  代码示例

Engine

  代码示例

容器在启动时的加载过程

  前面已经知道,Tomcat在启动时会依据server.xml来创建server,server中包含一个service,service中包含engine, engine中包含host,但一般到host就结束了,那我的应用程序(host里的context,context里的wrapper)是在什么时候完成加载的呢。
  这就不得不提到HostConfig(类似的EngineConfig/ContextConfig/ServletConfig)了,上面的代码中可以看到有XXXconfig的类,这些就是完成容器配置的观察者,只有这些观察者正确完成了资源的加载,Tomcat才能够正常启动.
当Host容器调用启动方法时,会通知观察者(包括HostConfig)来执行对应的操作。

public class HostConfig
    implements LifecycleListener, Runnable {
    public void lifecycleEvent(LifecycleEvent event) {
        // Identify the host we are associated with
        //...
       
        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.START_EVENT))
            start();
        else if (event.getType().equals(Lifecycle.STOP_EVENT))
            stop();
    }
    
    protected void start() {
        if (debug >= 1)
            log(sm.getString("hostConfig.start"));
        if (host.getAutoDeploy()) {
            deployApps();
        }
        if (isLiveDeploy()) {
            threadStart();
        }
    }
    
    //加载host目录下的描述符、war包、文件夹,都会成为Context
    protected void deployApps() {
        if (!(host instanceof Deployer))
            return;
        if (debug >= 1)
            log(sm.getString("hostConfig.deploying"));

        File appBase = appBase();
        if (!appBase.exists() || !appBase.isDirectory())
            return;
        String files[] = appBase.list();

        // 下面这三个操作就是找到应用程序所在的目录,进行加载
        // 一个目录就是一个Context
        deployDescriptors(appBase, files);
        deployWARs(appBase, files);
        deployDirectories(appBase, files);
    }
}

  创建web aplication的代码(在StandardHostDeployer的install函数中)

// 先实例化Conext(一般为StandardContext, 
// 然后设置目录,
// 再添加ContextConfig观察者),最后将它加入到Host的子容器集合中
// Install the new web application
Class clazz = Class.forName(host.getContextClass());
Context context = (Context) clazz.newInstance();
context.setPath(contextPath);

context.setDocBase(docBase);
if (context instanceof Lifecycle) {
    clazz = Class.forName(host.getConfigClass());
    LifecycleListener listener =
        (LifecycleListener) clazz.newInstance();
    ((Lifecycle) context).addLifecycleListener(listener);
}
host.fireContainerEvent(PRE_INSTALL_EVENT, context);
host.addChild(context);
host.fireContainerEvent(INSTALL_EVENT, context);
总结

  四种容器每个都有自己的管道,且都是在基本阀中依据某些规则,将对应的请求分配给对应的子容器,层层分配,最后到对应的servlet进行处理.