Container是容器的父接口,所有子容器都必须实现这个接口,Container容器的设计用的是典型的责任链的设计模式,他由4个子容器组件构成,分别实Engine、Host、Context和Wrapper,这4个组件不是平行的,而是父子关系,Engine包含Host,Host包含Context,Context包含Wrapper。通常一个Servlet class对应一个Wrapper,如果有多个Servlet,则可以定义多个Wrapper;如果有多个Wrapper,则要定义一个更高的Container,如Context,Context通常对应如下的配置:

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

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

这里看到了Value,是不是很熟悉?没错,Value的设计在其他框架中也有用到,同样Pipeline的原理基本上也是相似的。他是一个管道,Engine和Host都会执行这个Pipeline,你可以在这个管道上增加任意的Value,Tomcat会挨个执行这些Value,而且4个组件都会有自己的一套Value集合。你怎么才能定义自己的Value呢?在server.xml文件中可以添加,如给Engine和Host增加一个Value,代码如下:

<Engine defaultHost="localhost" name="Catalina">
    <Value className="org.apache.catalina.values.RequestDumperValue" />
    ......
    <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">
        <Value className="org.apache.catalina.values.FastCommonAccessLogValue" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false" />
        ......
    </Host>
</Engine>

StandardEngineValue、StandardHostValue是Engine和Host默认的Value,最后一个Value负责将请求传给他们的子容器,以继续往下执行。

前面是Engine和Host容器的请求过程,下面看Context和Wrapper容器是如何处理请求的。下图是处理请求的时序图。

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

Engine容器

Engine容器比较简单,他定义了一些基本的关联关系。

他的标准实现类是StandardEngine,注意Engine没有父容器,如果调用setParent方法将会报错。添加的子容器也只能是Host类型的。

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

Host容器

Host是Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,他负责安装和展开这些应用,并且标识这个应用以便能够区分他们。他的子容器通常是Context,他除了关联子容器外,还保存一个主机应有的信息。

除了所有容器都继承的ContainerBase外,StandardHost还实现了Deployer接口。

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

Context容器

Context代表Servlet的Context,他具备了Servelt运行的基本环境,理论上只要有Context就能运行Servlet了。简单地Tomcat可以没有Engine和Host。

Context最重要的功能就是管理他里面的Servlet实例,Servlet实例在Context中是以Wrapper出现的。还有一点就是Context如何才能找到正确的Servlet来执行他呢?Tomcat 5以前是通过一个Mapper类来管理的,在Tomcat 5 以后这个功能被移到了Request中,在前面的时序图中就可以发现获取子容器都是通过Request来分配的。

Context准备Servlet的运行环境是从Start方法开始的。他的主要作用是设置各种资源属性和管理组件,还有一个非常重要的作用就是启动子容器和Pipeline。

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

<Context path="/library/" docBase="D:\projects\library\deploy\target\library.war" reloadable="true" />

当这个reloadable设为true时,war被修改后Tomcat会自动重新加载这个应用。如何做到这点呢?这个功能是在StandardContext的backgroundProcess方法中实现的。

他会调用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是一个非常重要的方法,他基本上描述了对Servlet的操作,装载了Servlet后就会调用Servlet的init方法,同时会传一个StandardWrapperFacade对象给Servlet,这个对象包装了StandardWrapper.

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

当Servlet被初始化完成后,就等着StandardWrapperValue去调用他的Servlice方法了,调用Service方法之前要调用Servlet所有的filter。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐