深度剖析tomcat简介


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方法了。示例代码如下:

 

java代码:
  1. HttpConnector connector = new HttpConnector();   
  2.     SimpleContainer container = new SimpleContainer();   
  3. connector.setContainer(container);  

首先要注意的是,在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都调用结束。下面是伪代码:

 

java代码:
  1. // invoke each valve added to the pipeline for (int n=0; n<valves.length; n++) {   
  2.    valve[n].invoke( ... );   
  3. }   
  4. // then, invoke the basic valve   
  5. basicValve.invoke( ... );  

但是,tomcat的设计者选择了另一种实现方法,通过引入接口org.apache.catalina.ValveContext来实现。当connector调用container的invoke方法后,container要做的事并没有硬编码写在invoke方法中,而是调用pipeline的invoke方法,该方法的签名与container的invoke方法相同:

 

java代码:
  1. public void invoke(Request request, Response response) throws IOException, ServletException;  
  2.   
  3.     下面是org.apache.catalina.core.ContainerBase的invoke实现:  
  4.     public void invoke(Request request, Response response) throws IOException, ServletException {   
  5. pipeline.invoke(request, response);   
  6. }  

其中pipeline是Pipeline接口的实例。

 

pipeline必须保证添加到其中的所有valve和basic valve都被调用一次。pipeline是通过创建一个ValveContext实例来实现的。ValveContext是作为pipeline的一个内部类实现的,这样,ValveContext实例就可以访问pipeline的所有成员了。ValveContext接口中最重要的方法是invokeNext:

 

java代码:
  1. public void invokeNext(Request request, Response response) throws IOException, ServletException;  

 

在所有的container中,org.apache.catalina.core.StandardPipeline类实现了Pipeline接口,在该类中有一个内部类StandardPipelineValveContext实现了ValveContext接口。StandardPipelineValveContext的代码如下所示:

 

java代码:
  1. protected class StandardPipelineValveContext implements ValveContext {   
  2.    protected int stage = 0;   
  3.    public String getInfo() {   
  4.      return info;   
  5.    }   
  6.    public void invokeNext(Request request, Response response) throws IOException, ServletException {   
  7.      int subscript = stage;   
  8.      stage = stage + 1;   
  9.      // Invoke the requested Valve for the current request thread   
  10.      if (subscript < valves.length) {   
  11.        valves[subscript].invoke(request, response, this);   
  12.      }   
  13.      else if ((subscript == valves.length) && (basic != null)) {   
  14.        basic.invoke(request, response, this);   
  15.      }   
  16.      else {   
  17.        throw new ServletException   
  18.          (sm.getString("standardPipeline.noValve"));   
  19.      }   
  20.    }   
  21. }  

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。代码如下:

 

java代码:
  1. package org.apache.catalina.core;   
  2.   
  3. import java.io.IOException;   
  4. import javax.servlet.ServletException;   
  5. import org.apache.catalina.Request;   
  6. import org.apache.catalina.Response;   
  7. import org.apache.catalina.Valve;   
  8. import org.apache.catalina.ValveContext;   
  9. import org.apache.catalina.util.StringManager;   
  10.    
  11. public final class StandardValveContext implements ValveContext {   
  12.    protected static StringManager sm =   
  13.      StringManager.getManager(Constants.Package);   
  14.    protected String info =   
  15.      "org.apache.catalina.core.StandardValveContext/1.0";   
  16.    protected int stage = 0;   
  17.    protected Valve basic = null;   
  18.    protected Valve valves[] = null;   
  19.    public String getInfo() {   
  20.      return info;   
  21.    
  22.    }   
  23.    
  24.    public final void invokeNext(Request request, Response response)   
  25.      throws IOException, ServletException {   
  26.      int subscript = stage;   
  27.      stage = stage + 1;   
  28.      // Invoke the requested Valve for the current request thread   
  29.      if (subscript < valves.length) {        valves[subscript].invoke(request, response, this);   
  30.      }   
  31.      else if ((subscript == valves.length) && (basic != null)) {   
  32.        basic.invoke(request, response, this);   
  33.      }   
  34.      else {   
  35.        throw new ServletException   
  36.          (sm.getString("standardPipeline.noValve"));   
  37.      }   
  38.    }   
  39.    
  40.    void set(Valve basic, Valve valves[]) {   
  41.      stage = 0;   
  42.      this.basic = basic;   
  43.      this.valves = valves;   
  44.    }   
  45. }  

5.2.1  Pipeline

container调用pipeline的invoke方法开始对valve进行逐个调用。使用Pipeline接口的addValve方法可以添加新的valve,或调用removeValve方法删除对某个valve的调用。使用setBasicValve和getBasicValve可以对basic valve进行设置。

         Pipeline接口定义如下:


java代码:
  1. package org.apache.catalina;   
  2. import java.io.IOException;   
  3.       
  4. import javax.servlet.ServletException;    
  5. public interface Pipeline {   
  6. public Valve getBasic();   
  7. public void setBasic(Valve valve);   
  8. public void addValve(Valve valve);   
  9. public Valve[] getValves();   
  10. public void invoke(Request request, Response response) throws IOException, ServletException;   
  11. public void removeValve(Valve valve);   
  12. }  

5.2.2  Valve接口

Valve接口用于处理一个请求,包含了两个方法,invoke和getInfo。getInfo方法返回valve的实现信息Valve接口的定义如下:

 

java代码:
  1. package org.apache.catalina;   
  2. import java.io.IOException;   
  3. import javax.servlet.ServletException;   
  4.    
  5. public interface Valve {   
  6.    public String getInfo();   
  7.    public void invoke(Request request, Response response,   
  8.      ValveContext context) throws IOException, ServletException;   
  9. }  

5.2.3  ValveContext接口

该接口有两个方法,invokeNext和getInfo。接口定义如下:

 

java代码:
  1. package org.apache.catalina;  
  2. import java.io.IOException;   
  3. import javax.servlet.ServletException;   
  4.   
  5.     public interface ValveContext {   
  6.    public String getInfo();   
  7.    public void invokeNext(Request request, Response response)   
  8.      throws IOException, ServletException;   
  9.    
  10. }  

5.2.4  Contained接口

一个valve也可以实现org.apache.catalina.Contained接口,该接口将valve与某个container绑定。接口定义如下:

 

java代码:
  1. package org.apache.catalina;   
  2. public interface Contained {   
  3.     public Container getContainer();   
  4.     public void setContainer(Container 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类。方法签名如下:

 

java代码:
  1. public javax.servlet.Servlet allocate() throws javax.servlet.ServletException;   
  2. public void load() throws javax.servlet.ServletException;  

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实例使用。代码如下:

 

java代码:
  1. public SimpleLoader() {   
  2.      try {   
  3.        URL[] urls = new URL[l];   
  4.        URLStreamHandler streamHandler = null;   
  5.        File classPath = new File(WEB_ROOT);   
  6.        String repository = (new URL("file"null,   
  7.          classPath.getCanonicalPath() + File.separator)).toString() ;   
  8.        urls[0] = new URL(null, repository, streamHandler);   
  9.        classLoader = new URLClassLoader(urls);   
  10.      }   
  11.      catch (IOException e) {   
  12.        System.out.println(e.toString() );   
  13.      }   
  14. }  

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方法,代码如下:

 

java代码:
  1. public Loader getLoader() {   
  2. if (loader != null)   
  3. return (loader);   
  4. if (parent != null)   
  5. return (parent.getLoader());   
  6. return (null);   
  7. }  

该方法返回一个用于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如下所示:


java代码:
  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {   
  2. SimpleWrapper wrapper = (SimpleWrapper) getContainer();   
  3. ServletRequest sreq = request.getRequest();   
  4. ServletResponse sres = response.getResponse();   
  5. Servlet servlet = null;   
  6. HttpServletRequest hreq = null;   
  7. if (sreq instanceof HttpServletRequest)   
  8. hreq = (HttpServletRequest) sreq;   
  9. HttpServletResponse hres = null;   
  10. if (sres instanceof HttpServletResponse)   
  11. hres = (HttpServletResponse) sres;   
  12. //Allocate a servlet instance to process this request   
  13. try {   
  14. servlet = wrapper.allocate();   
  15. if (hres!=null && hreq!=null) {   
  16. servlet.service(hreq, hres);   
  17. else {   
  18. servlet.service(sreq, sres);        
  19. }   
  20. catch (ServletException e) { }   
  21. }  

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接口定义如下所示:

 

java代码:
  1.     package org.apache.catalina;   
  2. public interface Mapper {   
  3.         public Container getContainer();   
  4. public void setContainer(Container container);   
  5. public String getProtocol();   
  6. public void setProtocol(String protocol);   
  7. public Container map(Request request, boolean update);   
  8. }  

其中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方法,具体实现如下:


java代码:
  1. public void invoke(Request request, Response response,    ValveContext valveContext)   
  2.    throws IOException, ServletException {   
  3.    // Validate the request and response object types   
  4.    if (!(request.getRequest() instanceof HttpServletRequest) ||   
  5.      !(response.getResponse() instanceof HttpServletResponse)) {   
  6.      return;   
  7.    }   
  8.    
  9.    // Disallow any direct access to resources under WEB-INF or META-INF   
  10.    HttpServletRequest hreq = (HttpServletRequest) request.getRequest();   
  11.    String contextPath = hreq.getContextPath();   
  12.    String requestURI = ((HttpRequest) request).getDecodedRequestURI();   
  13.    String relativeURI =   
  14.      requestURI.substring(contextPath.length()).toUpperCase();   
  15.    
  16.    Context context = (Context) getContainer();   
  17.    // Select the Wrapper to be used for this Request   
  18.    
  19.   Wrapper wrapper = null;   
  20.    try {   
  21.      wrapper = (Wrapper) context.map(request, true);   
  22.    }   
  23.    catch (IllegalArgumentException e) {   
  24.      badRequest(requestURI, (HttpServletResponse)   
  25.        response.getResponse());   
  26.      return;   
  27.    }   
  28.    if (wrapper == null) {   
  29.      notFound(requestURI, (HttpServletResponse) response.getResponse());   
  30.      return;   
  31.    }   
  32.    // Ask this Wrapper to process this Request   
  33.    response.setContext(context);   
  34.    wrapper.invoke(request, response);   
  35. }  

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。

5.6.4  ex05.pyrmont.startup.Bootstrap2



转自:http://sishuok.com/forum/blogPost/list/4089.html

注:有一本书,《深入剖析tomcat》这本书很好,大家可以看一下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值