Tomcat体系架构

Tomcat体系架构

img

  • Server: A Server element represents the entire Catalina servlet container. (Singleton),一个Server包括多个Service。

    server组件是管理tomcat实例的组件,可以监听一个端口,从此端口上可以远程向该实例发送shutdown关闭命令。

  • Service:

    它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。

  • Connector:

    TOMCAT有两个典型的Connector,一个直接侦听来自browser的http请求,一个侦听来自其它WebServer的请求Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求 Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。

connector组件是监听组件,它有四个作用:

  • (1).开启监听套接字,监听外界请求,并和客户端建立TCP连接;

  • (2).使用protocolHandler解析请求中的协议和端口等信息,如http协议、AJP协议;

  • (3).根据解析到的信息,使用processer将分析后的请求转发给绑定的Engine;

  • (4).接收响应数据并返回给客户端。

  • Engine:

Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理,Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理。

容器用于从connector组件处接收已建立的TCP连接,还用于接收客户端发送的http请求并分析请求,然后按照分析的结果将相关参数传递给匹配出的虚拟主机。engine还用于指定默认的虚拟主机。

  • Host:

    代表一个Virtual Host,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==”"的Context将成为该Host的默认Context,所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。

  • Context:

一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成,Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回。

 

图 1.Tomcat 的总体结构

每个context都应该算是一个webapp,其路径由docBase决定,该目录存放的是归档的war文件或未归档的webapp相关文件,而host容器中的appBase则是虚拟主机整理webapp的地方,一个appBase下可以有多个webapp,即多个context

<host name=www.a.com appBase=/www/a >
    <context path="" docBase=/www/a />
    <context path="/xuexi" docBase=/www/a/xuexi />
</host>
  • executor

组件为每个Service组件提供线程池,使得各个connector和Engine可以从线程池中获取线程处理请求,从而实现tomcat的并发处理能力。一定要注意,Executor的线程池大小是为Engine组件设置,而不是为Connector设置的,Connector的线程数量由Connector组件的acceptorThreadCount属性来设置。如果要在配置文件中设置该组件,则必须设置在Connector组件的前面,以便在Connector组件中使用executor属性来引用配置好的Executor组件。如果不显式设置,则采用Connector组件上的默认配置,默认配置如下:

  • (1).maxThreads:最大线程数,默认值200。

  • (2).minSpareThreads:最小空闲线程数,默认值25。

  • (3).maxIdleTime:空闲线程的线程空闲多长时间才会销毁,默认值60000即1分钟。

  • (4).prestartminSpareThreads:是否启动executor时就直接创建等于最小空闲线程数的线程,默认值为false,即只在有连接请求进入时才会创建。

根据上面描述的tomcat组件体系结构,处理请求的大致过程其实很容易推导出来:

Client(request)-->Connector-->Engine-->Host-->Context-->Wrapper(response data)-->Connector(response header)-->Client

被嵌套类realm

realm定义的是一个安全上下文,就像是以哪种方式存储认证时的用户和组相关的数据库。

被嵌套类valve

Valve中文意思是阀门,类似于过滤器,它可以工作于Engine和Host/Context之间、Host和Context之间以及Context和Web应用程序的某资源之间。一个容器内可以建立多个Valve,而且Valve定义的次序也决定了它们生效的次序。

以 Service 作为“婚姻”

我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情侣的话,Connector 主要负责对外交流,可以比作为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作为 Girl。那么这个 Service 就是连接这对男女的结婚证了。是 Service 将它们连接在一起,共同组成一个家庭。当然要组成一个家庭还要很多其它的元素。

说白了,Service 只是在 Connector 和 Container 外面多包一层,把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。这个 Service 接口的方法列表如下:

图 2. Service 接口

图 2. Service 接口

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

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

图 3. 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);
}

这段代码很简单,其实就是先判断当前的这个 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 方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。

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

以 Server 为“居”

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

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

Server 的类结构图如下:

图 4. 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 中也是没有什么变化的。

Connector 组件

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

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

由于这个过程比较复杂,大体的流程可以用下面的顺序图来解释:

图 6. Connector 处理一次请求顺序图

img

Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是可以选择替换的。Connector 最重要的功能就是接收连接请求然后分配线程让 Container 来处理这个请求,所以这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化,它将 Connector 划分成 Connector、Processor、Protocol, 另外 Coyote 也定义自己的 Request 和 Response 对象。

下面主要看一下 Tomcat 中如何处理多线程的连接请求,先看一下 Connector 的主要类图:

图 7. Connector 的主要类图

img

看一下 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 来处理了。

Connector的结构图

img

Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。

其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。

  • Endpoint用来处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将Request交给Container进行具体的处理。

  • Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理。

  • Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket,在内部调用Processor进行处理。

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 引擎

Context和Host的区别是Context表示一个应用,我们的Tomcat中默认的配置下webapps下的每一个文件夹目录都是一个Context,其中ROOT目录中存放着主应用,其他目录存放着子应用,而整个webapps就是一个Host站点。

在这里插入图片描述

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

图 8. 四个容器的关系图

img

当 Connector 接受到一个连接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet 处理。下面是这个过程的时序图:

图 9. Engine 和 Host 处理请求的时序图

img

这里看到了 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 的处理请求时序图

img

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

Engine 容器

Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图如下:

图 11. 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 相关的类图

img

 

从上图中可以看出除了所有容器都继承的 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 的各种信息打交道。

Wrapper 容器对应servlet的处理过程。它开启servlet的生命周期,根据context给出的信息以及解析web.xml中的映射关系,负责装载相关的类,初始化servlet对象init()、执行servlet代码service()以及服务结束时servlet对象的销毁destory()。

下面看一下非常重要的一个方法 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 的关系

图 13. ServletConfig 与 StandardWrapperFacade、StandardWrapper 的关系

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

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

Tomcat 中其它组件

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

Tomcat运行流程

 

img

Tomcat运行流程

假设来自客户的请求为:http://localhost:8080/test/index.jsp

  1. 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得

  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应

  3. Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host

  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)

  5. localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context

  6. Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)

  7. path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet

  8. Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类

  9. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法

  10. Context把执行完了之后的HttpServletResponse对象返回给Host

  11. Host把HttpServletResponse对象返回给Engine

  12. Engine把HttpServletResponse对象返回给Connector

  13. Connector把HttpServletResponse对象返回给客户browser

Tomcat架构解析(一)-----Tomcat总体架构

Tomcat(二):tomcat配置文件server.xml详解和部署简介

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值