容器是一个处理用户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, ServletException
public 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(); } } } }