容器是一个处理用户servlet请求并返回对象给web用户的模块。
org.apache.catalina.Container接口定义了容器的形式,有四种容器:Engine(引擎), Host(主机), Context(上下文), 和 Wrapper(包装器)。
容器接口
一个容器必须实现org.apache.catalina.Container接口
传递一个Container实例给Connector对象的setContainer方法,然后Connector对象就可以使用container的invoke方法
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
对于Catalina的容器首先需要注意的是它一共有四种不同的容器:
Engine:表示整个Catalina的servlet引擎
Host:表示一个拥有数个上下文的虚拟主机
Context:表示一个Web应用,一个context包含一个或多个wrapper
Wrapper:表示一个独立的servlet
每一个概念之上是用org.apache.catalina包来表示的。
Engine、Host、Context和Wrapper接口都实现了Container即可。
它们的标准实现是StandardEngine, StandardHost, StandardContext, and StandardWrapper,它们都是org.apache.catalina.core包的一部分。
一个Catalina功能部署不一定需要所有的四种类型的容器。例如本章的第一个应用程序仅仅包括一个wrapper,而第二个应用程序是一个包含Context和wrapper的容器模块。
一个容器可以有一个或多个低层次上的子容器。例如,一个Context有一个或多个wrapper,而wrapper作为容器层次中的最底层,不能包含子容器。
Container接口被设计成Tomcat管理员可以通过server.xml文件配置来决定其工作方式的模式Pipelining Tasks(流水线任务)
一个pipeline包含了改容器要唤醒的所有任务。每一个阀门表示了一个特定的任务。一个容器的流水线有一个基本的阀门,但是你可以添加任意你想要添加的阀门。
阀门的数目定义为添加的阀门的个数(不包括基本阀门)。有趣的是,阀门可以通过编辑Tomcat的配置文件server.xml来动态的添加。理解了servlet过滤器,那么流水线和它的阀门的工作方式不难想象。
一个流水线就像一个过滤链,每一个阀门像一个过滤器。跟过滤器一样,一个阀门可以操作传递给它的request和response方法。
让一个阀门完成了处理,则进一步处理流水线中的下一个阀门,基本阀门总是在最后才被调用。一个容器可以有一个流水线。当容器的invoke方法被调用的时候,容器将会处理流水线中的阀门,并一个接一个的处理,直到所有的阀门都被处理完毕
流水线的invoke方法的伪代码如下所示
// invoke each valve added to the pipeline
for (int n=0; n<valves.length; n++) {
valve[n].invoke( ... );
}
// then, invoke the basic valve
basicValve.invoke( ... ); Tomcat的设计者选择了一种通过org.apache.catalina.ValveContext定义的方式来处理,这里介绍它如何工作的:
容器的invoke方法在被connector调用的时候所作的工作不难进行编码。容器调用的是流水线的invoke方法。
流水线接口的invoke方法前面跟容器接口的invoke方法签名相同
public void invoke(Request request, Response response) throws IOException, ServletException;
这里是Container接口中invoke方法在org.apache.catalina.core.ContainerBase的实现:
public void invoke(Request request, Response response)throws IOException, ServletException {
pipeline.invoke(request, response);
}
Pipeline是容器中Pipeline接口的一个实例。
流水线必须保证说要添加给它的阀门必须被调用一次,流水线通过创建一个ValveContext接口的实例来实现它。
ValveContext是流水线的的内部类,这样ValveContext就可以访问流水线中所有的成员。ValveContext中最重要的方法是invokeNext方法
public void invokeNext(Request request, Response response) throws IOException, ServletException
在创建一个ValveContext实例之后,流水线调用ValveContext的invokeNext方法。ValveContext会先唤醒流水线的第一个阀门,然后第一个阀门会在完成它的任务之前唤醒下一个阀门。
ValveContext将它自己传递给每一个阀门,那么该阀门就可以调用ValveContext的invokeNext方法。Valve接口的invoke签名如下:
public void invoke(Request request, Response response, ValveContext ValveContext) throws IOException, ServletExceptionpublic void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {
// Pass the request and response on to the next valve in our pipeline
valveContext.invokeNext(request, response);
// now perform what this valve is supposed to do ...
}org.apache.catalina.core.StandardPipeline类是容器流水线的实现
InvokeNext方法使用下标(subscript)和级别(stage)记住哪个阀门被唤醒。当第一次唤醒的时候,下标的值是0,级的值是1。
以你次,第一个阀门被唤醒,流水线的阀门获得ValveContext实例调用它的invokeNext方法。这时下标的值是1所以下一个阀门被唤醒,然后一步步的进行。The Pipeline Interface流水线接口
我们提到的流水线的第一个方法是它的Pipeline接口的invoke方法,该方法会开始唤醒流水线的阀门。流水线接口允许你添加一个新的阀门或者删除一个阀门。
最后,可以使用setBasic方法来分配一个基本阀门给流水线,getBasic方法会得到基本阀门。最后被唤醒的基本阀门,负责处理request和回复response。
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);
}The Valve Interface阀门接口
阀门接口表示一个阀门,该组件负责处理请求。该接口有两个方法,invoke和getInfo方法。
Invoke方法如上所述,getInfo方法返回阀门的信息
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
}The ValveContext Interface阀门上下文接口
阀门上下文接口有两个方法,invokeNext方法如上所述,getInfo方法会返回阀门上下文的信息。ValveContext接口如下:
public interface ValveContext {
public String getInfo();
public void invokeNext(Request request, Response response)throws IOException, ServletException;
}The Contained Interface Contained接口
一个阀门可以选择性的实现org.apache.catalina.Contained接口。该接口定义了其实现类跟一个容器相关联
public interface Contained {
public Container getContainer();
public void setContainer(Container container);
}the Wrapper Interface Wrapper接口
org.apache.catalina.Wrapper接口表示了一个包装器。一个包装器是表示一个独立servlet定义的容器。
包装器继承了Container接口,并且添加了几个方法。包装器的实现类负责管理其下层servlet的生命中期,包括servlet的init,service,和destroy方法。
由于包装器是最底层的容器,所以不可以将子容器添加给它。如果addChild方法被调用的时候会产生IllegalArgumantException异常。
包装器接口中重要方法有allocate和load方法。allocate方法负责定位该包装器表示的servlet的实例
Load方法负责load和初始化servlet的实例The Context Interface上下文(Context)接口
一个context在容器中表示一个web应用。一个context通常含有一个或多个包装器作为其子容器。
重要的方法包括addWrapper, createWrapper等方法The Wrapper Application(包装器应用程序)
这个应用程序展示了如何写一个简单的容器模型
核心类是SimpleWrapper,它实现了Wrapper接口。SimpleWrapper类包括一个Pipeline和一个Loader类来加载一个servlet。
流水线包括一个基本阀门SimpleWrapperValve和两个另外的阀门ClientIPLoggerValve,HeaderLoggerValve)
public static void main(String[] args) {
/**
* 初始化连接器
*/
HttpConnector connector = new HttpConnector();
//创建一个包装器
Wrapper wrapper = new SimpleWrapper();
//告诉包装器要加载类的名字
wrapper.setServletClass("ModernServlet");
//创建了加载器
Loader loader = new SimpleLoader();
//两个阀门
Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve();
//把加载器给包装器
wrapper.setLoader(loader);
//流水线添加俩个阀门
((Pipeline) wrapper).addValve(valve1);
((Pipeline) wrapper).addValve(valve2);
//连接器设置容器
//把包装器当做容器添加到连接器中,然后初始化并启动连接器
connector.setContainer(wrapper);
try{
//连接器初始化,实现Lifecycle周期接口
connector.initialize();
//启动
connector.start();
System.in.read();
}catch(Exception e){
e.printStackTrace();
}
}
SimpleWrapper
package com.tomcat.core;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import javax.naming.directory.DirContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import org.apache.catalina.AccessLog;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.InstanceListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Manager;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
/**
* 代表一个容器
* @author Administrator
*
*/
public class SimpleWrapper implements Wrapper {
/**
* loader变量用于加载一个servlet类
*/
private Loader loader;
/**
* Parent变量表示该包装器的父容器
*/
protected Container parent = null;
/**
* getLoader方法用于返回一个Loader对象用于加载一个servlet类。
* 如果一个包装器跟一个加载器相关联,会返回该加载器。
* 否则返回其父容器的加载器,如果没有父容器,则返回null。
*/
public Loader getLoader() {
if(loader!=null)
return loader;
if(parent!=null)
return parent.getLoader();
return null;
}
public void addInitParameter(String arg0, String arg1) {
// TODO Auto-generated method stub
}
public void addInstanceListener(InstanceListener arg0) {
// TODO Auto-generated method stub
}
.......
}
SimpleLoader
package com.tomcat.core;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import org.apache.catalina.Container;
import org.apache.catalina.Loader;
/**
* 容器中加载servlet的任务被分配给了Loader实现
* impleLoader就是一个Loader实现。
* 它知道如何定位一个servlet,并且通过getClassLoader获得一个java.lang.ClassLoader实例用来查找servlet类位置
* @author Administrator
*
*/
public class SimpleLoader implements Loader {
WEB_ROOT用来指明在哪里查找servlet类
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
ClassLoader classLoader = null;
Container container = null;
/**
* 初始化类加载器
*/
public SimpleLoader(){
try {
URL[] urls = new URL[1];
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 (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void addPropertyChangeListener(PropertyChangeListener arg0) {
// TODO Auto-generated method stub
}
......................
}
HeaderLoggerValve
package com.tomcat.core;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.CometEvent;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
/**
* 是一个阀门
* 打印请求头部到控制台上
* @author Administrator
*
*/
public class HeaderLoggerValve implements Valve, Contained {
public void backgroundProcess() {
// TODO Auto-generated method stub
}
public void event(Request arg0, Response arg1, CometEvent arg2)
throws IOException, ServletException {
// TODO Auto-generated method stub
}
public String getInfo() {
// TODO Auto-generated method stub
return null;
}
public Valve getNext() {
// TODO Auto-generated method stub
return null;
}
public void invoke(Request arg0, Response arg1) throws IOException,
ServletException {
// TODO Auto-generated method stub
}
...................
}
ClientIPLoggerValve
package com.tomcat.core;
import java.io.IOException;
import javax.servlet.ServletException;
import org.apache.catalina.CometEvent;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
/**
* ClientIPLoggerValve是一个阀门,它打印出客户端的IP地址到控制台
* @author Administrator
*
*/
public class ClientIPLoggerValve implements Valve,Contained{
protected Container container;
................................
}
SimplePipeline
package com.tomcat.core;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
/**
* 流水线
* @author Administrator
*
*/
public class SimplePipeline implements Pipeline {
public void addValve(Valve arg0) {
// TODO Auto-generated method stub
}
public Valve getBasic() {
// TODO Auto-generated method stub
return null;
}
public Valve getFirst() {
// TODO Auto-generated method stub
return null;
}
public Valve[] getValves() {
// TODO Auto-generated method stub
return null;
}
public void removeValve(Valve arg0) {
// TODO Auto-generated method stub
}
public void setBasic(Valve arg0) {
// TODO Auto-generated method stub
}
}
一个仅仅包括一个包装器的简单的web应用。该程序仅仅包括一个servlet
大多数的网络应用需要多个servlet。在这些应用中,需要一个跟包装器不同的容器:上下文。
如何使用一个包含两个包装器的上下文来包装两个servlet类。
当有多于一个得包装器的时候,需要一个map来处理这些子容器,对于特殊的请求可以使用特殊的子容器来处理。
在这个程序中,mapper是SimpleContextMapper类的一个实例,
它继承了Tomcat 4中的org.apache.catalina.Mapper接口。一个容器也可以有多个mapper来支持多协议。
例如容器可以用一个mapper来支持HTTP协议,而使用另一个mapper来支持HTTPS协议
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);
}
getContainer返回该容器的mapper,
setContainer方法用于联系一个容器到mapper。
getProtocol返回该mapper负责处理的协议,
setProtocol用于分配该容器要处理的协议。
map方法返回处理一个特殊请求的子容器。
SimpleContext表示一个上下文,它使用SimpleContextMapper作为它的mapper,SimpleContextValve作为它的基本阀门。
该上下文包括两个阀门ClientIPLoggerValve和HeaderLoggerValve。
用SimpleWrapper表示的两个包装器作为该上下文的子容器被添加。
包装器吧SimpleWrapperValve作为它的基本阀门,但是没有其它的阀门了。
该上下文应用程序使用同一个加载器、两个阀门。但是加载器和阀门时跟该上下文关联的,而不是跟包装器关联。
这样,两个加载器就可以都使用该加载器。该上下文被当做连接器的容器
基本流程如下
1. 一个容器有一个流水线,容器的invoke方法会调用流水线的invoke方法。
2. 流水线的invoke方法会调用添加到容器中的阀门的invoke方法,然后调用基本阀门的invoke方法。
3. 在一个包装器中,基本阀门负责加载相关的servlet类并对请求作出相应。
4. 在一个有子容器的上下文中,基本法门使用mapper来查找负责处理请求的子容器。如果一个子容器被找到,子容器的invoke方法会被调用,然后返回步骤1。
package com.tomcat.startup;
import org.apache.catalina.Context;
import org.apache.catalina.Loader;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import com.tomcat.core.ClientIPLoggerValve;
import com.tomcat.core.HeaderLoggerValve;
import com.tomcat.core.HttpConnector;
import com.tomcat.core.Mapper;
import com.tomcat.core.SimpleContext;
import com.tomcat.core.SimpleContextMapper;
import com.tomcat.core.SimpleLoader;
import com.tomcat.core.SimpleWrapper;
public final class Bootstrap2 {
/**
* @param args
*/
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2);
Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve();
((Pipeline) context).addValve(valve1);
((Pipeline) context).addValve(valve2);
Mapper mapper = new SimpleContextMapper();
mapper.setProtocol("http");
context.addMapper(mapper);
Loader loader = new SimpleLoader();
context.setLoader(loader);
// context.addServletMapping(pattern, name);
context.addServletMapping("/Primitive", "Primitive");
context.addServletMapping("/Modern", "Modern");
connector.setContainer(context);
try {
connector.initialize();
connector.start();
// make the application wait until we press a key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}