首页 > 代码库 > Tomcat 系统架构与设计模式,第 1 部分: 工作原理

Tomcat 系统架构与设计模式,第 1 部分: 工作原理

这个分为两个部分的系列文章将研究 Apache Tomcat 的系统架构以及其运用的非常多经典设计模式。本文是第 1 部分。将主要从 Tomcat 怎样分发请求、怎样处理多用户同一时候请求,还有它的多级容器是怎样协调工作的角度来分析 Tomcat 的工作原理,这也是一个 Web server首要解决的关键问题。


本文以 Tomcat 5 为基础。也兼顾最新的 Tomcat 6 和 Tomcat 4。

Tomcat 的基本设计思路和架构是具有一定连续性的。

Tomcat 整体结构

Tomcat 的结构非常复杂,可是 Tomcat 也非常的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。

以下是 Tomcat 的整体结构图:

图 1.Tomcat 的整体结构
技术分享

从上图中能够看出 Tomcat 的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面具体介绍。

Connector 组件是能够被替换,这样能够提供给server设计者很多其它的选择,由于这个组件是如此重要,不仅跟server的设计的本身,并且和不同的应用场景也十分相关,所以一个 Container 能够选择相应多个 Connector。多个 Connector 和一个 Container 就形成了一个 Service,Service 的概念大家都非常熟悉了,有了 Service 就能够对外提供服务了。可是 Service 还要一个生存的环境,必需要有人可以给她生命、掌握其生死大权,那就非 Server 莫属了。

所以整个 Tomcat 的生命周期由 Server 控制。

以 Service 作为“婚姻”

我们将 Tomcat 中 Connector、Container 作为一个总体比作一对情侣的话,Connector 主要负责对外交流,能够比作为 Boy。Container 主要处理 Connector 接受的请求,主要是处理内部事务,能够比作为 Girl。那么这个 Service 就是连接这对男女的结婚证了。

是 Service 将它们连接在一起。共同组成一个家庭。

当然要组成一个家庭还要非常多其他的元素。

说白了。Service 仅仅是在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务。一个 Service 能够设置多个 Connector。可是仅仅能有一个 Container 容器。

这个 Service 接口的方法列表例如以下:

图 2. Service 接口
技术分享

从 Service 接口中定义的方法中能够看出,它主要是为了关联 Connector 和 Container,同一时候会初始化它以下的其他组件,注意接口中它并没有规定一定要控制它以下的组件的生命周期。全部组件的生命周期在一个 Lifecycle 的接口中控制。这里用到了一个重要的设计模式,关于这个接口将在后面介绍。

Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅实现了 Service 借口同一时候还实现了 Lifecycle 接口,这样它就能够控制它以下的组件的生命周期了。StandardService 类结构图例如以下:

图 3. StandardService 的类结构图
技术分享

从上图中能够看出除了 Service 接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现。还有几个方法是用于在事件监听的方法的实现,不仅是这个 Service 组件,Tomcat 中其他组件也相同有这几个方法,这也是一个典型的设计模式。将在后面介绍。

以下看一下 StandardService 中基本的几个方法实现的代码,以下是 setContainer 和 addConnector 方法的源代码:

清单 1. StandardService. SetContainer
public void setContainer(Container container) {
    Container oldContainer = this.container;
    if ((oldContainer != null) && (oldContainer instanceof Engine))
        ((Engine) oldContainer).setService(null);
    this.container = container;
    if ((this.container != null) && (this.container instanceof Engine))
        ((Engine) this.container).setService(this);
    if (started && (this.container != null) && (this.container instanceof Lifecycle)) {
        try {
            ((Lifecycle) this.container).start();
        } catch (LifecycleException e) {
            ;
        }
    }
    synchronized (connectors) {
        for (int i = 0; i < connectors.length; i++)
            connectors[i].setContainer(this.container);
    }
    if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {
        try {
            ((Lifecycle) oldContainer).stop();
        } catch (LifecycleException e) {
            ;
        }
    }
    support.firePropertyChange("container", oldContainer, this.container);
}

这段代码非常easy,事实上就是先推断当前的这个 Service 有没有已经关联了 Container,假设已经关联了,那么去掉这个关联关系—— oldContainer.setService(null)。假设这个 oldContainer 已经被启动了,结束它的生命周期。然后再替换新的关联、再初始化并開始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是。改动 Container 时要将新的 Container 关联到每一个 Connector,还好 Container 和 Connector 没有双向关联,不然这个关联关系将会非常难维护。

清单 2. StandardService. addConnector
public void addConnector(Connector connector) {
    synchronized (connectors) {
        connector.setContainer(this.container);
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;
        if (initialized) {
            try {
                connector.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }
        if (started && (connector instanceof Lifecycle)) {
            try {
                ((Lifecycle) connector).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("connector", null, connector);
    }
}

上面是 addConnector 方法,这种方法也非常easy。首先是设置关联关系,然后是初始化工作,開始新的生命周期。

这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑能够理解。有趣的是这里用了数组可是并没有向我们寻常那样,一開始就分配一个固定大小的数组,它这里的实现机制是:又一次创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这样的方式实现了类似的动态数组的功能,这样的实现方式,值得我们以后拿来借鉴。

最新的 Tomcat6 中 StandardService 也基本没有变化。可是从 Tomcat5 開始 Service、Server 和容器类都继承了 MBeanRegistration 接口。Mbeans 的管理更加合理。

以 Server 为“居”

前面说一对情侣由于 Service 而成为一对夫妻,有了可以组成一个家庭的基本条件,可是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就行安心的为人民服务了。一起为社会创造財富。

Server 要完毕的任务非常easy。就是要可以提供一个接口让其他程序可以訪问到这个 Service 集合、同一时候要维护它所包括的全部 Service 的生命周期。包括怎样初始化、怎样结束服务、怎样找到别人要訪问的 Service。还有其他的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关日常的安全检查什么的。

Server 的类结构图例如以下:

图 4. Server 的类结构图
技术分享

它的标准实现类 StandardServer 实现了上面这些方法,同一时候也实现了 Lifecycle、MbeanRegistration 两个接口的全部方法,以下主要看一下 StandardServer 重要的一个方法 addService 的实现:

清单 3. StandardServer.addService
public void addService(Service service) {
    service.setServer(this);
    synchronized (services) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;
        if (initialized) {
            try {
                service.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }
        if (started && (service instanceof Lifecycle)) {
            try {
                ((Lifecycle) service).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("service", null, service);
    }
}

从上面第一句就知道了 Service 和 Server 是相互关联的,Server 也是和 Service 管理 Connector 一样管理它,也是将 Service 放在一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生命周期。

Tomcat6 中也是没有什么变化的。

组件的生命线“Lifecycle”

前面一直在说 Service 和 Server 管理它以下组件的生命周期,那它们是怎样管理的呢?

Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件仅仅要继承这个接口并实现当中的方法就能够统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就能够控制 Tomcat 中全部组件的生命周期,这个最高的组件就是 Server。而控制 Server 的是 Startup。也就是您启动和关闭 Tomcat。

以下是 Lifecycle 接口的类结构图:

图 5. Lifecycle 类结构图
技术分享

除了控制生命周期的 Start 和 Stop 方法外另一个监听机制,在生命周期開始和结束的时候做一些额外的操作。这个机制在其他的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。

Lifecycle 接口的方法的实现都在其他组件中。就像前面中说的,组件的生命周期由包括它的父组件控制,所以它的 Start 方法自然就是调用它以下的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法。Server 的 Start 方法代码例如以下:

清单 4. StandardServer.Start
public void start() throws LifecycleException {
    if (started) {
        log.debug(sm.getString("standardServer.start.started"));
        return;
    }
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
    synchronized (services) {
        for (int i = 0; i < services.length; i++) {
            if (services[i] instanceof Lifecycle)
                ((Lifecycle) services[i]).start();
        }
    }
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

监听的代码会包围 Service 组件的启动过程。就是简单的循环启动全部 Service 组件的 Start 方法。可是全部 Service 必需要实现 Lifecycle 接口。这样做会更加灵活。

Server 的 Stop 方法代码例如以下:

清单 5. StandardServer.Stop
public void stop() throws LifecycleException {
    if (!started)
        return;
    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;
    for (int i = 0; i < services.length; i++) {
        if (services[i] instanceof Lifecycle)
            ((Lifecycle) services[i]).stop();
    }
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

它所要做的事情也和 Start 方法几乎相同。

Connector 组件

Connector 组件是 Tomcat 中两个核心组件之中的一个,它的主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要做的事了。

因为这个过程比較复杂。大体的流程能够用以下的顺序图来解释:

图 6. Connector 处理一次请求顺序图
技术分享

(查看清晰大图)

Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是能够选择替换的。Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必定是多线程的,多线程的处理是 Connector 设计的核心。

Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象。

以下主要看一下 Tomcat 中怎样处理多线程的连接请求。先看一下 Connector 的主要类图:

图 7. Connector 的主要类图
技术分享

(查看清晰大图)

看一下 HttpConnector 的 Start 方法:

清单 6. HttpConnector.Start
public void start() throws LifecycleException {
    if (started)
        throw new LifecycleException
            (sm.getString("httpConnector.alreadyStarted"));
    threadName = "HttpConnector[" + port + "]";
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
    threadStart();
    while (curProcessors < minProcessors) {
        if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
            break;
        HttpProcessor processor = newProcessor();
        recycle(processor);
    }
}

threadStart() 运行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续运行,这个激活是在 HttpProcessor 的 assign 方法中,这种方法是代码例如以下 

清单 7. HttpProcessor.assign
synchronized void assign(Socket socket) {
    while (available) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    this.socket = socket;
    available = true;
    notifyAll();
    if ((debug >= 1) && (socket != null))
        log(" An incoming request is being assigned");
}

创建 HttpProcessor 对象是会把 available 设为 false,所以当请求到来时不会进入 while 循环,将请求的 socket 赋给当期处理的 socket,并将 available 设为 true,当 available 设为 true 是 HttpProcessor 的 run 方法将被激活。接下去将会处理这次请求。

Run 方法代码例如以下:

清单 8. HttpProcessor.Run
public void run() { 
    while (!stopped) { 
        Socket socket = await(); 
        if (socket == null) 
            continue; 
        try { 
            process(socket); 
        } catch (Throwable t) { 
            log("process.invoke", t); 
        } 
        connector.recycle(this); 
    } 
    synchronized (threadSync) { 
        threadSync.notifyAll(); 
    } 
}

解析 socket 的过程在 process 方法中,process 方法的代码片段例如以下:

清单 9. HttpProcessor.process
 private void process(Socket socket) {
    boolean ok = true;
    boolean finishResponse = true;
    SocketInputStream input = null;
    OutputStream output = null;
    try {
        input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());
    } catch (Exception e) {
        log("process.create", e);
        ok = false;
    }
    keepAlive = true;
    while (!stopped && ok && keepAlive) {
        finishResponse = true;
        try {
            request.setStream(input);
            request.setResponse(response);
            output = socket.getOutputStream();
            response.setStream(output);
            response.setRequest(request);
            ((HttpServletResponse) response.getResponse())
				.setHeader("Server", SERVER_INFO);
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }
        try {
            if (ok) {
                parseConnection(socket);
                parseRequest(input, output);
                if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
                    parseHeaders(input);
                if (http11) {
                    ackRequest(output);
                    if (connector.isChunkingAllowed())
                        response.setAllowChunking(true);
                }
            }
        。。

。。。

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

。。。

。。 } try { shutdownInput(input); socket.close(); } catch (IOException e) { ; } catch (Throwable e) { log("process.invoke", e); } socket = null; }

当 Connector 将 socket 连接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。

Servlet 容器“Container”

Container 是容器的父接口,全部子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,各自是:Engine、Host、Context、Wrapper,这四个组件不是平行的。而是父子关系,Engine 包括 Host,Host 包括 Context。Context 包括 Wrapper。通常一个 Servlet class 相应一个 Wrapper。假设有多个 Servlet 就能够定义多个 Wrapper。假设有多个 Wrapper 就要定义一个更高的 Container 了。如 Context,Context 通常就是相应以下这个配置:

清单 10. Server.xml
<Context 
    path="/library"
    docBase="D:\projects\library\deploy\target\library.war" 
    reloadable="true"
/>

容器的整体设计

Context 还能够定义在父容器 Host 中,Host 不是必须的,可是要执行 war 程序。就必需要 Host。由于 war 中必有 web.xml 文件,这个文件的解析就需要 Host 了,假设要有多个 Host 就要定义一个 top 容器 Engine 了。

而 Engine 没有父容器了。一个 Engine 代表一个完整的 Servlet 引擎。

那么这些容器是怎样协同工作的呢?先看一下它们之间的关系图:

图 8. 四个容器的关系图
技术分享

(查看清晰大图)

当 Connector 接受到一个连接请求时,将请求交给 Container。Container 是怎样处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是怎样将终于的请求交给 Servlet 处理。

以下是这个过程的时序图:

图 9. Engine 和 Host 处理请求的时序图
技术分享

(查看清晰大图)

这里看到了 Valve 是不是非常熟悉。没错 Valve 的设计在其它框架中也实用的,相同 Pipeline 的原理也基本是相似的。它是一个管道,Engine 和 Host 都会运行这个 Pipeline,您能够在这个管道上添加随意的 Valve,Tomcat 会挨个运行这些 Valve,并且四个组件都会有自己的一套 Valve 集合。您怎么才干定义自己的 Valve 呢?在 server.xml 文件里能够加入,如给 Engine 和 Host 添加一个 Valve 例如以下:

清单 11. Server.xml
<Engine defaultHost="localhost" name="Catalina">

    <Valve className="org.apache.catalina.valves.RequestDumperValve"/>
    ………
    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" 
	    xmlNamespaceAware="false" xmlValidation="false">

        <Valve className="org.apache.catalina.valves.FastCommonAccessLogValve"
            directory="logs"  prefix="localhost_access_log." suffix=".txt"
            pattern="common" resolveHosts="false"/>	   
    …………
    </Host>
</Engine>

StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下运行。

前面是 Engine 和 Host 容器的请求过程。以下看 Context 和 Wrapper 容器时怎样处理请求的。

以下是处理请求的时序图:

图 10. Context 和 wrapper 的处理请求时序图
技术分享

(查看清晰大图)

从 Tomcat5 開始,子容器的路由放在了 request 中。request 中保存了当前请求正在处理的 Host、Context 和 wrapper。

Engine 容器

Engine 容器比較简单,它仅仅定义了一些主要的关联关系。接口类图例如以下:

图 11. Engine 接口的类结构
技术分享

它的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,假设调用 setParent 方法时将会报错。加入子容器也仅仅能是 Host 类型的。代码例如以下:

清单 12. StandardEngine. addChild
public void addChild(Container child) {
    if (!(child instanceof Host))
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notHost"));
    super.addChild(child);
}

public void setParent(Container container) {
    throw new IllegalArgumentException
        (sm.getString("standardEngine.notParent"));
}

它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。

Host 容器

Host 是 Engine 的字容器,一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是执行多个应用,它负责安装和展开这些应用,而且标识这个应用以便可以区分它们。它的子容器一般是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。

以下是和 Host 相关的类关联图:

图 12. Host 相关的类图
技术分享

(查看清晰大图)

从上图中能够看出除了全部容器都继承的 ContainerBase 外,StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每一个 web application。

Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法,Host 能够调用这些方法完毕应用的部署等。

Context 容器

Context 代表 Servlet 的 Context。它具备了 Servlet 执行的基本环境,理论上仅仅要有 Context 就能执行 Servlet 了。简单的 Tomcat 能够没有 Engine 和 Host。

Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,另一点就是 Context 怎样才干找到正确的 Servlet 来运行它呢? Tomcat5 曾经是通过一个 Mapper 类来管理的,Tomcat5 以后这个功能被移到了 request 中,在前面的时序图中就能够发现获取子容器都是通过 request 来分配的。

Context 准备 Servlet 的执行环境是在 Start 方法開始的。这种方法的代码片段例如以下:

清单 13. StandardContext.start
public synchronized void start() throws LifecycleException {
    ………
    if( !initialized ) { 
        try {
            init();
        } catch( Exception ex ) {
            throw new LifecycleException("Error initializaing ", ex);
        }
    }
    
	………
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    setAvailable(false);
    setConfigured(false);
    boolean ok = true;
    File configBase = getConfigBase();
    if (configBase != null) {
        if (getConfigFile() == null) {
            File file = new File(configBase, getDefaultConfigFile());
            setConfigFile(file.getPath());
            try {
                File appBaseFile = new File(getAppBase());
                if (!appBaseFile.isAbsolute()) {
                    appBaseFile = new File(engineBase(), getAppBase());
                }
                String appBase = appBaseFile.getCanonicalPath();
                String basePath = 
                    (new File(getBasePath())).getCanonicalPath();
                if (!basePath.startsWith(appBase)) {
                    Server server = ServerFactory.getServer();
                    ((StandardServer) server).storeContext(this);
                }
            } catch (Exception e) {
                log.warn("Error storing config file", e);
            }
        } else {
            try {
                String canConfigFile =  (new File(getConfigFile())).getCanonicalPath();
                if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {
                    File file = new File(configBase, getDefaultConfigFile());
                    if (copy(new File(canConfigFile), file)) {
                        setConfigFile(file.getPath());
                    }
                }
            } catch (Exception e) {
                log.warn("Error setting config file", e);
            }
        }
    }

    ………
    Container children[] = findChildren();
    for (int i = 0; i < children.length; i++) {
        if (children[i] instanceof Lifecycle)
            ((Lifecycle) children[i]).start();
    }
    
	if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();
    ………

}

它主要是设置各种资源属性和管理组件,还有很重要的就是启动子容器和 Pipeline。

我们知道 Context 的配置文件里有个 reloadable 属性,如以下配置:

清单 14. Server.xml
<Context 
    path="/library" 
    docBase="D:\projects\library\deploy\target\library.war" 
    reloadable="true" 
/>

当这个 reloadable 设为 true 时,war 被改动后 Tomcat 会自己主动的又一次载入这个应用。怎样做到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这种方法的代码例如以下:

清单 15. StandardContext. backgroundProcess
public void backgroundProcess() {
    if (!started) return;
    count = (count + 1) % managerChecksFrequency;
    if ((getManager() != null) && (count == 0)) {
        try {
            getManager().backgroundProcess();
        } catch ( Exception x ) {
            log.warn("Unable to perform background process on manager",x);
        }
    }
    if (getLoader() != null) {
        if (reloadable && (getLoader().modified())) {
            try {
                Thread.currentThread().setContextClassLoader
                    (StandardContext.class.getClassLoader());
                reload();
            } finally {
                if (getLoader() != null) {
                    Thread.currentThread().setContextClassLoader
                        (getLoader().getClassLoader());
                }
            }
        }
        if (getLoader() instanceof WebappLoader) {
            ((WebappLoader) getLoader()).closeJARs(false);
        }
    }
}

它会调用 reload 方法,而 reload 方法会先调用 stop 方法然后再调用 Start 方法,完毕 Context 的一次又一次载入。能够看出运行 reload 方法的条件是 reloadable 为 true 和应用被改动,那么这个 backgroundProcess 方法是怎么被调用的呢?

这种方法是在 ContainerBase 类中定义的内部类 ContainerBackgroundProcessor 被周期调用的。这个类是运行在一个后台线程中,它会周期的运行 run 方法。它的 run 方法会周期调用全部容器的 backgroundProcess 方法。由于全部容器都会继承 ContainerBase 类,所以全部容器都可以在 backgroundProcess 方法中定义周期运行的事件。

Wrapper 容器

Wrapper 代表一个 Servlet。它负责管理一个 Servlet,包含的 Servlet 的装载、初始化、运行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

Wrapper 的实现类是 StandardWrapper。StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各种信息打交道。

以下看一下很重要的一个方法 loadServlet。代码片段例如以下:

清单 16. StandardWrapper.loadServlet
public synchronized Servlet loadServlet() throws ServletException {
    ………
    Servlet servlet;
    try {
        ………
        ClassLoader classLoader = loader.getClassLoader();
        ………
        Class classClass = null;
        ………
        servlet = (Servlet) classClass.newInstance();
        if ((servlet instanceof ContainerServlet) &&
            (isContainerProvidedServlet(actualClass) ||
            ((Context)getParent()).getPrivileged() )) {
                ((ContainerServlet) servlet).setWrapper(this);
        }
        classLoadTime=(int) (System.currentTimeMillis() -t1);
        try {
            instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);
            if( System.getSecurityManager() != null) {
                Class[] classType = new Class[]{ServletConfig.class};
                Object[] args = new Object[]{((ServletConfig)facade)};
                SecurityUtil.doAsPrivilege("init",servlet,classType,args);
            } else {
                servlet.init(facade);
            }
            if ((loadOnStartup >= 0) && (jspFile != null)) {
                ………
                if( System.getSecurityManager() != null) {
                    Class[] classType = new Class[]{ServletRequest.class,
                        ServletResponse.class};
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service",servlet,classType,args);
                } else {
                    servlet.service(req, res);
                }
            }
            instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);
            ………
        
	return servlet;
}

它基本上描写叙述了对 Servlet 的操作,当装载了 Servlet 后就会调用 Servlet 的 init 方法,同一时候会传一个 StandardWrapperFacade 对象给 Servlet,这个对象包装了 StandardWrapper,ServletConfig 与它们的关系图例如以下:

图 13. ServletConfig 与 StandardWrapperFacade、StandardWrapper 的关系
技术分享

Servlet 能够获得的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。所以 Servlet 能够通过 ServletConfig 拿到有限的容器的信息。

当 Servlet 被初始化完毕后。就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法之前要调用 Servlet 全部的 filter。

Tomcat 中其他组件

Tomcat 还有其他重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其他组件。这些组件共同为 Connector 和 Container 提供必要的服务。

參考资料

学习

  • 查看本系列的第 2 部分:“Tomcat 设计模式分析”。

  • “面向0基础 Web 开发者的 Tomcat”(developerWorks,2005 年 10 月):Apache Tomcat 应用server不再是高级 Web 系统开发者的专用领域。

    在本教程中,Sing Li 将向0基础 Web 开发者展示怎样利用他们当前的 Java 开发技能,使用 Tomcat 编写server端 JSP、servlet 和 Web 服务。

  • 技术书店:浏览关于这些和其它技术主题的图书。
  • developerWorks Java 技术专区:数百篇关于 Java 编程各个方面的文章。

讨论

  • 增加 developerWorks 社区。
  • 查看 developerWorks 博客 的最新信息。

Tomcat 系统架构与设计模式,第 1 部分: 工作原理