Tomcat4x5x内部的Container接口和Wrapper容器的学习
servlet容器是用来处理请求servlet资源,并为web客户端填充rsponse对象的模块。servletring器是org.apache.catalina.Container接口的示例。在Tomcat中,有4中类型的容器,分别是
- Engine(表示整个Catalina servlet引擎)
- Host(表示包含有一个或多个Context容器的虚拟主机)
- Context(表示一个Web应用程序。一个Context可以有多个Wrapper)
- Wrapper(表示一个独立的servlet)
这里学习Container接口和Context和Wrapper两种容器。
Container接口
Tomcat中的servlet容器必须实现Container接口。然后将本实例传入到连接器的setContainer()方法中。这里连接器才能调用servlet容器的invoke()方法。
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
以上的四个servlet容器都继承了Container接口。类图如下(找不到高清的)
一个容器可以由0个或多个低层级的子容器。一般情况下,一个Context实例会有一个或者多个wrapper实例,一个Host实例中会有0个或多个Context实例。但是,Wrapper类型处于层级结构的最底层。因此它无法在包含子容器了。。可以通过Container接口的addChild()方法向某容器中添加纵容其。removeChild()方法删除子容器。
容器可以包含一些支持的组件,如载入器、记录器、管理器、领域和资源等。可以通过Container接口的addChild提供了与这些组件关联的方法。同事满足在部署应用时,tomcat管理员可以通过编辑配置文件server.xml来决定使用哪种容器。是通过引入容器中的管道(pipeline)和阀(value)的集合实现的
管道任务
管道包含该servlet容器将要调用的任务。一个阀表示一个具体的执行任务。在servlet容器管道中,有一个基础阀,可以增加任意数量的阀,不包括基础阀。
阀1 阀2 阀3 .... 阀n 基础阀
管道和servlet的过滤器链一样,而阀和过滤器一样,可以处理传递给它的request对象和response对象。当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行。
一个servlet容器可以有一条管道。当调用了容器是invoke()方法后,容器会将处理工作交由管道完成,而管道会调用其中的第一个阀开始处理。直到处理完所有的阀。伪代码如下:
//阀的循环调用
for(int n = 0;n< values.length;n++){
value[n].invoke(...);
}
//基础阀的调用
basicValue.invoke(...);
tomcat中,作者提供了 pipeline接口、value接口、valuecontext接口、Contained接口等来实现以上过程。示例是 org.apache.catalina.core.ContainerBase容器类的invoke()实现。
这里并没有直接在容器本身的invoke方法中编写任务处理逻辑,而是调用了pipeline的invoke()方法,该pipeline是实例化了org.apache.catalina.core.StandardPipeline,
protected Pipeline pipeline = new StandardPipeline(this);
public void invoke(Request request, Response response)
throws IOException, ServletException
{
this.pipeline.invoke(request, response);
}
invoke()方法如下:
public void invoke(Request request, Response response)
throws IOException, ServletException
{
new StandardPipelineValveContext().invokeNext(request, response);
}
其中,StandardPipelineValveContext 实现了ValveContext接口,示例如下:
这里是tomcat4的写法,tomcat5移除了StandardPipelineValveContext,换了StandardValueContext累来实现。
protected class StandardPipelineValveContext
implements ValveContext
{
protected int stage = 0;
protected StandardPipelineValveContext() {}
public String getInfo()
{
return StandardPipeline.this.info;
}
public void invokeNext(Request request, Response response)
throws IOException, ServletException
{
int subscript = this.stage;
this.stage += 1;
if (subscript < StandardPipeline.this.valves.length) {
StandardPipeline.this.valves[subscript].invoke(request, response, this);
} else if ((subscript == StandardPipeline.this.valves.length) && (StandardPipeline.this.basic != null)) {
StandardPipeline.this.basic.invoke(request, response, this);
} else {
throw new ServletException(StandardPipeline.sm.getString("standardPipeline.noValve"));
}
}
}
以上的这种写法,我只是粗浅的认为作者将管道与阀的概念做了隔离及结构上的区分。比硬编码带来的好处是什么,除了结构划分外我领悟不到…. (mark一下)
在阅读源码时,要切记context、wrapper都是servlet容器,是tomcat内置的容器,HttpConnector连接器的setContainer(xx)方法会加载容器的配置结构等。如下:
package ex05.pyrmont.startup;
import ex05.pyrmont.core.SimpleLoader;
import ex05.pyrmont.core.SimpleWrapper;
import ex05.pyrmont.valves.ClientIPLoggerValve;
import ex05.pyrmont.valves.HeaderLoggerValve;
import org.apache.catalina.Loader;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap1 {
public static void main(String[] args) {
/* call by using http://localhost:8080/ModernServlet,
but could be invoked by any name */
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 {
connector.initialize();
connector.start();
// make the application wait until we press a key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Wrapper容器
这里的warpper容器将通过Loader接口的实例 SimpleLoader来完成servlet容器的载入,如下
SimpleLoader.java
public class SimpleLoader implements Loader {
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 (IOException e) {
System.out.println(e.toString() );
}
}
public ClassLoader getClassLoader() {
return classLoader;
}
....
}
SimpleWrapper.java
public class SimpleWrapper implements Wrapper, Pipeline {
// the servlet instance
private Servlet instance = null;
private String servletClass;
private Loader loader;
private String name;
private SimplePipeline pipeline = new SimplePipeline(this);
protected Container parent = null;
public SimpleWrapper() {
pipeline.setBasic(new SimpleWrapperValve());
}
public synchronized void addValve(Valve valve) {
pipeline.addValve(valve);
}
public Servlet allocate() throws ServletException {
// Load and initialize our instance if necessary
if (instance==null) {
try {
instance = loadServlet();
}
catch (ServletException e) {
throw e;
}
catch (Throwable e) {
throw new ServletException("Cannot allocate a servlet instance", e);
}
}
return instance;
}
private Servlet loadServlet() throws ServletException {
if (instance!=null)
return instance;
Servlet servlet = null;
String actualClass = servletClass;
if (actualClass == null) {
throw new ServletException("servlet class has not been specified");
}
Loader loader = getLoader();
// Acquire an instance of the class loader to be used
if (loader==null) {
throw new ServletException("No loader.");
}
ClassLoader classLoader = loader.getClassLoader();
// Load the specified servlet class from the appropriate class loader
Class classClass = null;
try {
if (classLoader!=null) {
classClass = classLoader.loadClass(actualClass);
}
}
catch (ClassNotFoundException e) {
throw new ServletException("Servlet class not found");
}
// Instantiate and initialize an instance of the servlet class itself
try {
servlet = (Servlet) classClass.newInstance();
}
catch (Throwable e) {
throw new ServletException("Failed to instantiate servlet");
}
// Call the initialization method of this servlet
try {
servlet.init(null);
}
catch (Throwable f) {
throw new ServletException("Failed initialize servlet.");
}
return servlet;
}
public String getInfo() {
return null;
}
public Loader getLoader() {
if (loader != null)
return (loader);
if (parent != null)
return (parent.getLoader());
return (null);
}
....
}
SimpleWrapper类有一个pipeline实例,并设置了基础阀,都是在其构造函数中完成。
SimpleWrapperValue是一个基础阀,专门用于处理SimpleWrapper类的请求。实现如下
public class SimpleWrapperValve implements Valve, Contained {
protected Container container;
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) {
}
}
public String getInfo() {
return null;
}
public Container getContainer() {
return container;
}
public void setContainer(Container container) {
this.container = container;
}
}
要注意其中实例的调用关系。servlet.service(hreq, hres)中的servlet是SimpleWrapperValve对象本身,而不是SimpleWrapper。
Context容器
表示一个web应用程序,其中可以包含一个或者多个Wrapper实例作为其子容器。当程序中有多个Wrapper实例时,需要使用一个映射器(组件),帮助servlet容器处理某个指定的请求。(只在tomcat4中,之后已经换了方式)
这里是映射器的实现示例SimpleContextMapper.java
package ex05.pyrmont.core;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Container;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.Mapper;
import org.apache.catalina.Request;
import org.apache.catalina.Wrapper;
public class SimpleContextMapper implements Mapper {
/**
* The Container with which this Mapper is associated.
*/
private SimpleContext context = null;
public Container getContainer() {
return (context);
}
public void setContainer(Container container) {
if (!(container instanceof SimpleContext))
throw new IllegalArgumentException
("Illegal type of container");
context = (SimpleContext) container;
}
public String getProtocol() {
return null;
}
public void setProtocol(String protocol) {
}
/**
* Return the child Container that should be used to process this Request,
* based upon its characteristics. If no such child Container can be
* identified, return <code>null</code> instead.
*
* @param request Request being processed
* @param update Update the Request to reflect the mapping selection?
*
* @exception IllegalArgumentException if the relative portion of the
* path cannot be URL decoded
*/
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath =
((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
String name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
return (wrapper);
}
}
这里是一个context加载多个Wrapper时的示例
package ex05.pyrmont.startup;
import ex05.pyrmont.core.SimpleContext;
import ex05.pyrmont.core.SimpleContextMapper;
import ex05.pyrmont.core.SimpleLoader;
import ex05.pyrmont.core.SimpleWrapper;
import ex05.pyrmont.valves.ClientIPLoggerValve;
import ex05.pyrmont.valves.HeaderLoggerValve;
import org.apache.catalina.Context;
import org.apache.catalina.Loader;
import org.apache.catalina.Mapper;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap2 {
public static void main(String[] args) {
//连接器
HttpConnector connector = new HttpConnector();
//servlet1容器
Wrapper wrapper1 = new SimpleWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
//servlet2容器
Wrapper wrapper2 = new SimpleWrapper();
wrapper2.setName("Modern");
wrapper2.setServletClass("ModernServlet");
//web应用程序容器
Context context = new SimpleContext();
context.addChild(wrapper1);
context.addChild(wrapper2);
//管道中的阀1、阀2
Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve();
//管道中添加阀
((Pipeline) context).addValve(valve1);
((Pipeline) context).addValve(valve2);
//当有多个wrapper是,使用映射器
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();
}
}
}
注: 到这里代码量和增加的设计思路已经比较多了,要结合源码 + 代码调试比较容易理解。
原创文章,转载请标明本文链接: Tomcat4x5x内部的Container接口和Wrapper容器的学习