您的位置:首页 > 运维架构 > Tomcat

深度剖析tomcat简介

2016-06-21 16:11 731 查看



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代码:

查看复制到剪贴板打印

HttpConnector connector = new HttpConnector();

SimpleContainer container = new SimpleContainer();

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代码:

查看复制到剪贴板打印

// invoke each valve added to the pipeline for (int n=0; n<valves.length; n++) {

valve
.invoke( ... );

}

// then, invoke the basic valve

basicValve.invoke( ... );

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

java代码:

查看复制到剪贴板打印

public void invoke(Request request, Response response) throws IOException, ServletException;

下面是org.apache.catalina.core.ContainerBase的invoke实现:

public void invoke(Request request, Response response) throws IOException, ServletException {

pipeline.invoke(request, response);

}

其中pipeline是Pipeline接口的实例。

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

java代码:

查看复制到剪贴板打印

public void invokeNext(Request request, Response response) throws IOException, ServletException;

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

java代码:

查看复制到剪贴板打印

protected class StandardPipelineValveContext implements ValveContext {

protected int stage = 0;

public String getInfo() {

return info;

}

public void invokeNext(Request request, Response response) throws IOException, ServletException {

int subscript = stage;

stage = stage + 1;

// Invoke the requested Valve for the current request thread

if (subscript < valves.length) {

valves[subscript].invoke(request, response, this);

}

else if ((subscript == valves.length) && (basic != null)) {

basic.invoke(request, response, this);

}

else {

throw new ServletException

(sm.getString("standardPipeline.noValve"));

}

}

}

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代码:

查看复制到剪贴板打印

package org.apache.catalina.core;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.catalina.Request;

import org.apache.catalina.Response;

import org.apache.catalina.Valve;

import org.apache.catalina.ValveContext;

import org.apache.catalina.util.StringManager;

public final class StandardValveContext implements ValveContext {

protected static StringManager sm =

StringManager.getManager(Constants.Package);

protected String info =

"org.apache.catalina.core.StandardValveContext/1.0";

protected int stage = 0;

protected Valve basic = null;

protected Valve valves[] = null;

public String getInfo() {

return info;

}

public final void invokeNext(Request request, Response response)

throws IOException, ServletException {

int subscript = stage;

stage = stage + 1;

// Invoke the requested Valve for the current request thread

if (subscript < valves.length) { valves[subscript].invoke(request, response, this);

}

else if ((subscript == valves.length) && (basic != null)) {

basic.invoke(request, response, this);

}

else {

throw new ServletException

(sm.getString("standardPipeline.noValve"));

}

}

void set(Valve basic, Valve valves[]) {

stage = 0;

this.basic = basic;

this.valves = valves;

}

}


5.2.1 Pipeline

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

Pipeline接口定义如下:

java代码:

查看复制到剪贴板打印

package org.apache.catalina;

import java.io.IOException;

import javax.servlet.ServletException;

public interface Pipeline {

public Valve getBasic();

public void setBasic(Valve valve);

public void addValve(Valve valve);

public Valve[] getValves();

public void invoke(Request request, Response response) throws IOException, ServletException;

public void removeValve(Valve valve);

}


5.2.2 Valve接口

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

java代码:

查看复制到剪贴板打印

package org.apache.catalina;

import java.io.IOException;

import javax.servlet.ServletException;

public interface Valve {

public String getInfo();

public void invoke(Request request, Response response,

ValveContext context) throws IOException, ServletException;

}


5.2.3 ValveContext接口

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

java代码:

查看复制到剪贴板打印

package org.apache.catalina;

import java.io.IOException;

import javax.servlet.ServletException;

public interface ValveContext {

public String getInfo();

public void invokeNext(Request request, Response response)

throws IOException, ServletException;

}


5.2.4 Contained接口

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

java代码:

查看复制到剪贴板打印

package org.apache.catalina;

public interface Contained {

public Container getContainer();

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代码:

查看复制到剪贴板打印

public javax.servlet.Servlet allocate() throws javax.servlet.ServletException;

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代码:

查看复制到剪贴板打印

public SimpleLoader() {

try {

URL[] urls = new URL[l];

URLStreamHandler streamHandler = null;

File classPath = new File(WEB_ROOT);

String repository = (new URL("file", null,

classPath.getCanonicalPath() + File.separator)).toString() ;

urls[0] = new URL(null, repository, streamHandler);

classLoader = new URLClassLoader(urls);

}

catch (IOException e) {

System.out.println(e.toString() );

}

}


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代码:

查看复制到剪贴板打印

public Loader getLoader() {

if (loader != null)

return (loader);

if (parent != null)

return (parent.getLoader());

return (null);

}

该方法返回一个用于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代码:

查看复制到剪贴板打印

public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {

SimpleWrapper wrapper = (SimpleWrapper) getContainer();

ServletRequest sreq = request.getRequest();

ServletResponse sres = response.getResponse();

Servlet servlet = null;

HttpServletRequest hreq = null;

if (sreq instanceof HttpServletRequest)

hreq = (HttpServletRequest) sreq;

HttpServletResponse hres = null;

if (sres instanceof HttpServletResponse)

hres = (HttpServletResponse) sres;

//Allocate a servlet instance to process this request

try {

servlet = wrapper.allocate();

if (hres!=null && hreq!=null) {

servlet.service(hreq, hres);

} else {

servlet.service(sreq, sres);

}

} catch (ServletException e) { }

}

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代码:

查看复制到剪贴板打印

package org.apache.catalina;

public interface Mapper {

public Container getContainer();

public void setContainer(Container container);

public String getProtocol();

public void setProtocol(String protocol);

public Container map(Request request, boolean update);

}

其中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代码:

查看复制到剪贴板打印

public void invoke(Request request, Response response, ValveContext valveContext)

throws IOException, ServletException {

// Validate the request and response object types

if (!(request.getRequest() instanceof HttpServletRequest) ||

!(response.getResponse() instanceof HttpServletResponse)) {

return;

}

// Disallow any direct access to resources under WEB-INF or META-INF

HttpServletRequest hreq = (HttpServletRequest) request.getRequest();

String contextPath = hreq.getContextPath();

String requestURI = ((HttpRequest) request).getDecodedRequestURI();

String relativeURI =

requestURI.substring(contextPath.length()).toUpperCase();

Context context = (Context) getContainer();

// Select the Wrapper to be used for this Request

Wrapper wrapper = null;

try {

wrapper = (Wrapper) context.map(request, true);

}

catch (IllegalArgumentException e) {

badRequest(requestURI, (HttpServletResponse)

response.getResponse());

return;

}

if (wrapper == null) {

notFound(requestURI, (HttpServletResponse) response.getResponse());

return;

}

// Ask this Wrapper to process this Request

response.setContext(context);

wrapper.invoke(request, response);

}


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》这本书很好,大家可以看一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomcat 对象 class