container用于处理对servlet的请求,并未客户端填充resposne对象。container由org.apache.catalina.Container接口表示。共有四种类型的container:engine,host,context和wrapper。本章涉及到的是context和wrapper。engine和host留在第13章讨论。本章以对Container接口的讨论开始,随后介绍container中的流水线(pipelining)机制。然后对Wrapper和Context接口进行介绍。
5.1 Container接口
container必须实现org.apache.catalina.Container接口。然后将container实例设置到connector的setContainer方法中。这样,connector就可以调用container的invoke方法了。示例代码如下:
首先要注意的是,在tomcat中,共有四种类型的container,分别由不同的概念层次:
l engine:表示tomcat的整个servlet引擎;
l host:表示包含有一个或多个context的虚拟机;
l context:表示一个web应用。一个context中可以有多个wrapper;
l wrapper:表示一个独立的servlet。
上述的每个概念层级由org.apache.catalina包内的一个接口表示,分别是Engine,Host,Context和Wrapper,它们都继承自Container接口。这四个接口的标准实现分别是StandardEngine,StandardHost,StandardContext和StandardWrapper,都在org.apache.catalina.core包内。下面的uml图展示了各类的关系:
图表 10 Container接口及其实现类的UML图示
注意,所有的实现类都继承自ContainerBase类。
catalina中的部署并不是必须将所有四种container都包括在内。例如,本章中第一个应用的container模块仅仅用到了wrapper。
一个container可以有0个或多个低层级的子container。例如,一般情况下,一个context中会有一个或多个wrapper,一个host中会有0个或多个context。但是,wrapper处理层级的最底层,因此,它无法再包含子container了。可以通过Container接口的addChild方法添加子container,removeChild删除一个子container,此外,Container接口也支持对子container的查找,方法是findChild和findChildren。
container中可以包含一些支持的组件,如Loader,logger,Manger,Realm和Resources等,提供了getter和setter方法进行访问。这些组件将在后续的章节中讨论。
有趣的是,Container接口被设计成,在部署应用是,tomcat管理员可以通过编辑选项文件(server.xml)来决定使用哪种container。这是通过引入流水线(pipeline)和container中阀(valve)的集合实现的。
5.2 流水线(pipeline)任务
本节说明在connector调用了container的invoke方法后发生了什么事。按照相关的四个接口分为四个小节讨论,这四个接口都在org.apache.catalina包中,分别是Pipeline,Valve,ValveContext和Contained。
pipeline包括了container中要执行的任务。一个阀(valve)表示一个指定的任务。在pipeline中有一个基础valve(basic valve),但是使用者可根据自己的需要添加其他的valve。注意,vavle的数量被定义为额外添加的valve的数量,不包括basic valve。有趣的是,valve可以通过修改server.xml来动态添加。
如果对servlet的过滤器有所了解的话,就不难理解pipeline和valve是如何工作的。pipeline就像是filter链,每个valve就像是一个过滤器。实际上,valve与过滤器类似,它可以控制传递给它的request和response对象。当一个valve处理结束后,它就调用pipeline中的下一个valve进行处理。basic valve总是最后被调用的。
container中有一个pipeline。当调用了container的invoke方法后,container将处理过程交给它的pipeline,而pipeline会 调用它的第一个valve,valve执行完后会调用后续的valve,知道所有的valve都调用结束。下面是伪代码:
但是,tomcat的设计者选择了另一种实现方法,通过引入接口org.apache.catalina.ValveContext来实现。当connector调用container的invoke方法后,container要做的事并没有硬编码写在invoke方法中,而是调用pipeline的invoke方法,该方法的签名与container的invoke方法相同:
其中pipeline是Pipeline接口的实例。
pipeline必须保证添加到其中的所有valve和basic valve都被调用一次。pipeline是通过创建一个ValveContext实例来实现的。ValveContext是作为pipeline的一个内部类实现的,这样,ValveContext实例就可以访问pipeline的所有成员了。ValveContext接口中最重要的方法是invokeNext:
在所有的container中,org.apache.catalina.core.StandardPipeline类实现了Pipeline接口,在该类中有一个内部类StandardPipelineValveContext实现了ValveContext接口。StandardPipelineValveContext的代码如下所示:
invokeNext方法是用变量subscript和stage表明要调用哪个valve。当第一个valve在pipeline的invoke方法中被调用时,subscript的值是0,stage的值为1。因此,第一个valve被调用。pipeline中的第一个valve接收ValveContext的实例,调用它的invokeNext方法。这时subscript的值为1,可以调用第二个valve。当invokeNext方法被最后一个valve调用时,subscript等于valve的数量,然后,调用basic valve。
tomcat5中从StandardPipeline取消了StandardPipelineValveContext,取而代之的是org.apache.catalina.core.StandardValveContext。代码如下:
5.2.1 Pipeline
container调用pipeline的invoke方法开始对valve进行逐个调用。使用Pipeline接口的addValve方法可以添加新的valve,或调用removeValve方法删除对某个valve的调用。使用setBasicValve和getBasicValve可以对basic valve进行设置。
Pipeline接口定义如下:
5.2.2 Valve接口
Valve接口用于处理一个请求,包含了两个方法,invoke和getInfo。getInfo方法返回valve的实现信息Valve接口的定义如下:
5.2.3 ValveContext接口
该接口有两个方法,invokeNext和getInfo。接口定义如下:
5.2.4 Contained接口
一个valve也可以实现org.apache.catalina.Contained接口,该接口将valve与某个container绑定。接口定义如下:
5.3 Wrapper应用程序
org.apache.catalina.Wrapper接口表示一个wrapper层级的container,表示一个独立的servlet定义。Wrapper继承自Container接口,又添加了一些额外的方法。Wrapper接口的实现要负责管理servlet的生民周期,例如,调用init,service,destroy等方法。若是向Wrapper的addChild方法被调用,则抛出IllegalArgumantException异常。
Wrapper接口中比较重要的方法是load和allocate方法。allocate方法会为wrapper分配一个servlet实例,而且,allocate方法还要考虑下servlet类是否实现了javax.servlet.SingleThreadModel接口(在11章讨论)。load方法载入并初始化servlet类。方法签名如下:
5.4 Context接口
Context表示了一个web应用,一个context中可以有一个或多个wrapper。Context接口比较重要的方法是addWrapper和createWrapper(在第十二章讨论)。
5.5 Wrapper程序实例
该程序展示了如何写一个最小的container模块。核心类是ex05.pyrmont.core.SimpleWrapper,实现了Wrapper接口。SimpleWrapper类中包含了一个Pipeline(由ex05.pyrmont.core.SimplePipeline实现),使用一个Loader(由ex05.pyrmont.core.SimpeLoader实现)载入servlet。pipeline中有一个basic valve(ex05.pyrmont.core.SimpleWrapperValve),和两个额外的valve(ex05.pyrmont.core.ClientIPLoggerValve和ex05.pyrmont.core.HeaderLoggerValve)。程序的uml图如下所示:
图表 11 Wrapper程序UML示意图
注意:程序使用tomcat4默认的connector。
5.5.1 ex05.pyrmont.core.SimpleLoader
该类用于在container中载入servlet类。该类通过变量WEB_ROOT指明要在哪里查找servlet类。使用ClassLoader和Container,分别指明类载入器和container。
SimpleLoader类的构造函数会初始化一个class loader,以便SimpleWrapper实例使用。代码如下:
5.5.2 ex05.pyrmont.core.SimplePipeline
SimplePipeline类实现了org.apache.catalina.Pipeline接口,最重要的是invoke方法,该方法中包含了一个内部类,SimplePipelineValveContext,该类实现了org.apache.catalina.ValveContext接口。
5.5.3 ex05.pyrmont.core.SimpleWrapper
该类实现了org.apache.catalina.Wrapper接口,提供了allocate和load方法的实现,声明了两个变量:
private Loader loader;
protected Container parent = null;
loader变量指明了载入servlet要使用的loader实例。parent变量指明该wrapper的父container。注意其中的getLoader方法,代码如下:
该方法返回一个用于servlet类的loader,若wrapper已经关联了一个loader,则直接将其返回,否则从父container处获取并返回,若还是没有,返回null。
SimpleWrapper有一个pipeline,需要通过setBasic方法为其设置一个basic valve。
5.5.4 ex05.pyrmont.core.SimpleWrapperValve
SimpleWrapperValve类实现了org.apache.catalina.Valve接口和org.apache.catalina.Contained接口,是一个basic valve,专用于为SimpleWrapper处理请求。其最重要的方法invoke如下所示:
SimpleWrapperValve类作为basic valve使用,因此,其invoke方法不需要调用valveContext的invokeNext方法,它调用servlet的service方法,而不是wrapper类的。
5.5.5 ex05.pyrmont.valves.ClientIPLoggerValve
该类打印用户的ip信息。代码如下:
注意其invoke方法,它先调用valveContext的invokeNext方法,然后再打印ip。
5.5.6 ex05.pyrmont.valves.HeaderLoggerValve
该类与ClientIPLoggerValve类似,打印请求头信息。代码如下:
5.5.7 ex05.pyrmont.startup.Bootstrap1
该类用于启动应用程序,代码如下:
5.6 Context程序实例
本节的程序展示了如何使用context和wrapper。在程序中是了一个mapper(一个组件)来帮助context选择某个wrapper来处理特殊的请求。
注意:mapper组件仅在tomcat4中,tomcat5使用了其他的方法。
本例中,mapper是ex05.pyrmont.core.SimpleContextMapper类的实例,该类实现了org.apache.catalina.Mapper接口。container中可以包含有多个mapper来支持不同的请求协议。例如,一个mapper处理HTTP协议请求,另一个mapper处理HTTPS协议的请求。org.apache.catalina.Mapper接口定义如下所示:
其中setProtocol和getProtocol指明了该mapper负责处理哪种协议,map方法返回使用哪个子container处理特殊的请求。相关类的UML图如下所示 :
图表 12 Context应用程序相关类 UML示意图
SimpleContext类表示context,SimpleContextMapper作为其mapper,SimpleContextValve作为其basic valve。context中有两个valve,ClientIPLoggerValve和HeaderLoggerValve,还有两个wrapper,都是SimpleWrapper,这两个wrapper都使用SimpleWrapperValve作为其basic valve,且不再添加其他valve。
在Context应用程序中,使用了相同的loader和valve,但是它们之间是通过context,而不是wrapper关联的,这样,所有的wrapper就都可以使用一个loader了。context作为connector的container使用,因此,当connector接收到一个http请求时,会调用context的invoke方法。剩下的步骤如下所示:
(1)container中包含一个pipeline,container的方法会调用pipeline的invoke方法;
(2)pipeline调用所有添加到其中的valve,最后调用basic valve;
(3)在wrapper中,basic valve负责载入servlet类,并相应http请求;
(4)在包含子container的context中,basic valve使用mapper找到某个子container负责处理http请求;若找到了这样的子container,则调用其invoke方法,调用第一步。
5.6.1 ex05.pyrmont.core.SimpleContextValve
该类中最重要的方法是invoke方法,具体实现如下:
5.6.2 ex05.pyrmont.core.SimpleContextMapper
SimpleContextMapper类实现了org.apache.catalina.Mapper接口。代码如下:
5.6.3 ex05.pyrmont.core.SimpleContext
SimpleContext类是本程序的context实现,是分配给connector的主container,但是对每个独立servlet的处理是由wrapper完成的。本程序中有两个servlet,两个wrapper,它们都有对应的名字,PrimitiveServlet对应的wrapper的名字是Primitive,ModernServlet对应的wrapper的名字是Modern。SimpleContext是通过url映射来决定调用哪个wrapper的。本程序有两个url可以使用,其中“/Primitive”会调用Primitive,“/Modern”会调用Modern。你也可以添加自己的映射。
Container和Context接口中有很多方法,在本程序的实现中,大部分都是空方法,但与映射有关的方法都实现了,这些方法是:
(1)addServletMapping,添加一个url和wrapper的映射;
(2)findServletMapping,通过url查找对应的wrapper;
(3)addMapper,在context中添加一个mapper。SimpleContext类中有两个变量mapper和mappers。mapper表示程序使用的默认mapper,mappers包含了SimpleContext实例中所有的mapper。第一个被添加到mappers中的mapper成为默认mapper;
(4)findMapper,找到正确的mapper,在SimpleContext中,它返回默认mapper;
(5)map,返回负责处理当前请求的wrapper。