【0】README
0.0)本文部分文字描述转自:“深入剖析tomcat”,旨在学习 tomcat(5)servlet容器 的基础知识;
0.1)intro to servlet容器:servlet容器是用来处理请求servlet资源,并为web客户端填充response 对象的模块;
0.2)补充:吃个饭过来,真心没想到这么多人围观,再次对文章重新排版(并不是博主我不给力,是CSDN编辑器时不时吊链子啊,oh),并添加了测试用例的程序流程图(共3张)[1604111947];
0.3)for complete source code, please visit  https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter5
0.4) 温馨建议:建议阅读本文之前,已阅读过 tomcat(1~4)的系列文章,因为它们是环环相扣的;
1)intro to servlet容器:servlet容器是用来处理请求servlet资源,并为web客户端填充response 对象的模块;
1.1)servlet容器:是 org.apache.catalina.Container接口的实例;
1.2)在Tomcat中,共有四种容器(types):
t1)Engine;
t2)Host;
t3)Context;
t4)Wrapper;
【1】Container接口
1)在Tomcat中,共有四种容器(types):(干货——Tomcat中共有4种容器)

t1)Engine:表示整个Catalina servlet 引擎;
t2)Host:表示包含有一个或多个 Context容器的虚拟主机;
t3)Context:表示一个web 应用程序,一个Context 可以有多个 Wrapper;
t4)Wrapper:表示一个独立的servlet;
2)以上4种容器都是 org.apache.catalina包下的接口:分别为Engine,Host, Context, Wrapper,他们都继承自Container接口。这4个接口的标准实现是 StandardEngine类,StandardHost类,StandardContext类,StandardWrapper类,他们都在 org.apache.catalina.core 包内;
Attention)
A1)所有的实现类都继承自抽象类 ContainerBase ;
A2)Container接口的设计满足以下条件:在部署应用时,Tomcat管理员可以通过编辑配置文件(server.xml)来决定使用哪种容器。这是通过引入容器中的管道(pipeline)和阀(valve)的集合实现的;(干货——引入了容器中的管道和阀)
【2】管道任务
1)本节旨在说明: 当连接器调用了servlet容器的invoke方法后会发生什么事情,并讨论org.apache.catalina 包中的4个相关接口,Pipeline, Valve, ValveContext 和 Contained;
2)管道和阀:(干货——注意管道和阀的定义)
2.1)管道:包含该servlet容器将要调用的任务;
2.2)一个阀:表示一个具体的任务。
2.3)在servlet容器的管道中,有一个基础阀,但是,可以添加任意数量的阀。阀的数量指的是额外添加的阀数量,即,不包括基础阀。有意思的是, 可以通过编辑tomcat 的 配置文件(server.xml)来动态地添加阀;
2.4)一条管道和阀的示意图如下:
Attention)
A1)管道就想过滤器链条一样,而阀则好似过滤器;
A2)当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行;(干货——当一个阀执行完成后,会调用下一个阀继续执行。基础阀总是最后一个执行)
3)管道的invoke方法:一个servlet容器可以有一条管道,当调用了容器的invoke方法后,容器会将处理工作交由管道完成,而管道会调用其中的第一个阀开始处理。当第一个阀处理完后,它会调用后续的阀继续执行任务,直到管道中所有的阀都处理完成。
 public void invoke(Request request, Response response) //SimplePipeline.invoke()方法throws IOException, ServletException {// Invoke the first Valve in this pipeline for this request(new SimplePipelineValveContext()).invokeNext(request, response);}public void removeValve(Valve valve) {}// this class is copied from org.apache.catalina.core.StandardPipeline class's// StandardPipelineValveContext inner class.protected class SimplePipelineValveContext implements ValveContext {protected int stage = 0;public String getInfo() {return null;}public void invokeNext(Request request, Response response)throws IOException, ServletException {int subscript = stage; // init stage == 0.stage = stage + 1;// Invoke the requested Valve for the current request threadif (subscript < valves.length) {valves[subscript].invoke(request, response, this); // 若非基础阀是 ClientIPLoggerValve}else if ((subscript == valves.length) && (basic != null)) {basic.invoke(request, response, this); // this line, 直到基础阀才停止遍历非基础阀}else {throw new ServletException("No valve");}}} // end of inner class
public class ClientIPLoggerValve implements Valve, Contained {protected Container container;public void invoke(Request request, Response response, ValveContext valveContext)throws IOException, ServletException {// Pass this request on to the next valve in our pipelinevalveContext.invokeNext(request, response); // 继续回调 SimplePipeline.invokeNext(),直到基础阀System.out.println("Client IP Logger Valve");ServletRequest sreq = request.getRequest();System.out.println(sreq.getRemoteAddr()); // this line outputs ip addr to the console.System.out.println("------------------------------------");}

4)实现阀的遍历:Tomcat引入接口 org.apache.catalina.ValveContext 来实现阀的遍历执行;
4.1)管道必须保证添加到其中的所有阀和基础阀都被调用一次:这是通过调用一个 ValveContext接口实例来实现的。
4.2)ValveContext接口中最重要的方法是 invokeNext方法:在创建了ValveContext实例后,管道会调用ValveContext实例的 invokeNext方法。ValveContext实例会首先调用管道中的 第一个阀,第一个阀执行完后,会调用后面的阀继续执行。ValveContext实例会将自身传给每个阀,因此,每个阀都可以调用 ValveContext实例的 invokeNext方法;
5)org.apache.catalina.core.StandardPipeline类: 是所有servlet容器中的Pipeline接口的实现,Tomcat4中有一个实现了ValveContext接口的内部类,名为StandardPipelineValveContext;
6)Tomcat5 从 StandardPipeline类中移除了 StandardPipelineValveContext类:却使用 org.apache.catalina.core.StandardValveContext类来调用阀;
【2.1】Pipeline接口
1)对于Pipeline接口:首先要提到的一个方法是 invoke方法,servlet容器调用invoke方法来开始调用管道中的阀和基础阀;
2)getBasic和setBasic:setBasic方法将基础阀设置到管道中,getBasic获取基础阀;(干货——管道中可以指定基础阀)
3)addValve和removeValve:新增阀和删除阀;(干货——在管道中可以新增和删除非基础阀)(请注意:SimplePipe.addValve()下面的 System.arraycopy 方法)
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("servlet.ModernServlet"); // 设置servlet的相对路径Loader loader = new SimpleLoader(); // 类加载器Valve valve1 = new HeaderLoggerValve(); // 把请求头信息output到 consoleValve valve2 = new ClientIPLoggerValve();// 用来将client的IP 地址输出到控制台上wrapper.setLoader(loader);((Pipeline) wrapper).addValve(valve1); // 新增阀 ,key line((Pipeline) wrapper).addValve(valve2); // 新增阀, key lineconnector.setContainer(wrapper);try {connector.initialize(); // 创建服务器套接字connector.start(); // // make the application wait until we press a key.System.in.read();}catch (Exception e) {e.printStackTrace();}}
}
  public synchronized void addValve(Valve valve) { // SimpleWrapper.addValve()pipeline.addValve(valve);}
public void addValve(Valve valve) { // SimplePipeline.addValve()if (valve instanceof Contained)((Contained) valve).setContainer(this.container);synchronized (valves) {Valve results[] = new Valve[valves.length +1];System.arraycopy(valves, 0, results, 0, valves.length);results[valves.length] = valve;valves = results;}}

【2.2】Valve接口
1)阀是Valve接口的实例,用来处理接收到的请求,有两个方法:invoke方法和getinfo方法;
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response,ValveContext context)throws IOException, ServletException;
}

【2.3.】ValveContext接口
1)有两个方法:invokeNext方法和 getInfo方法;
public interface ValveContext {
public String getInfo();
public void invokeNext(Request request, Response response)throws IOException, ServletException;
}

【2.4】Contained接口

public interface Contained {
public Container getContainer();
public void setContainer(Container container);
}

【3】Wrapper接口
1)intro to Wrapper:Wrapper级的servlet容器是一个 org.apache.catalina.Wrapper接口的实例,表示一个独立的servlet定义。Wrapper接口继承自 Container接口,又添加了一些额外的方法。
2)Wrapper接口的实现类:要负责管理继承servlet类的servlet生命周期,即,调用 servlet的 init(), service(), destroy()方法;
3)由于Wrapper已经是最低级的容器了,不能再向其中添加子容器;(干货——Wrapper已经是最低级的servlet容器)
4)Wrapper接口有两个方法:load方法 和 allocate方法;
4.1)load方法:载入并初始化servlet类;
4.2)allocate方法:会分配一个已经初始化的servlet实例;
 public Servlet allocate() throws ServletException { // SimpleWrapper.allocate() method.// Load and initialize our instance if necessaryif (instance==null) {try {instance = loadServlet(); // this line}catch (ServletException e) {throw e;}catch (Throwable e) {throw new ServletException("Cannot allocate a servlet instance", e);}}return instance;}private Servlet loadServlet() throws ServletException { // this line 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(); // this line// Acquire an instance of the class loader to be usedif (loader==null) {throw new ServletException("No loader.");}ClassLoader classLoader = loader.getClassLoader(); // this line// Load the specified servlet class from the appropriate class loaderClass 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 itselftry {servlet = (Servlet) classClass.newInstance();}catch (Throwable e) {throw new ServletException("Failed to instantiate servlet");}// Call the initialization method of this servlettry {servlet.init(null);}catch (Throwable f) {throw new ServletException("Failed initialize servlet.");}return servlet;}public Loader getLoader() { // this  lineif (loader != null)return (loader);if (parent != null)return (parent.getLoader());return (null);}

【4】Context接口
1)intro to Context:Context接口是一个web 应用程序,一个Context实例可以有一个或多个Wrapper实例作为其子容器;
2)比较重要的方法: addWrapper() and createWrapper();
【5】Wrapper 应用程序(demonstrate how to build a smallest servlet container)
1)SimpleWrapper类:该类实现了Wrapper接口,包含一个Pipeline实例,并使用Loader实例载入servlet类。Pipeline实例包含一个基础阀和两个额外的阀。
【5.1】SimpleLoader类
1)SimpleLoader:负责完成类的载入工作,它知道servlet类的位置,通过调用其getClassLoader可以返回一个 java.lang.ClassLoader实例,可以用来搜索servlet类的位置;
2)SimpleLoader的构造函数:会初始化类加载器,供 SimpleWrapper实例使用;
public class SimpleLoader implements Loader { // return class loaderpublic 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;}

【5.2】SimplePipeline类
1)该类最重要的方法是 invoke方法;
public class SimplePipeline implements Pipeline {public SimplePipeline(Container container) {setContainer(container);}protected Valve basic = null;protected Container container = null;protected Valve valves[] = new Valve[0];public void setContainer(Container container) {this.container = container;}public Valve getBasic() {return basic;}public void setBasic(Valve valve) {this.basic = valve;((Contained) valve).setContainer(container);}public void addValve(Valve valve) {if (valve instanceof Contained)((Contained) valve).setContainer(this.container);synchronized (valves) {Valve results[] = new Valve[valves.length +1];System.arraycopy(valves, 0, results, 0, valves.length);results[valves.length] = valve;valves = results;}}   public void invoke(Request request, Response response)throws IOException, ServletException {// Invoke the first Valve in this pipeline for this request(new SimplePipelineValveContext()).invokeNext(request, response);} protected class SimplePipelineValveContext implements ValveContext {protected int stage = 0;public String getInfo() {return null;}public void invokeNext(Request request, Response response)throws IOException, ServletException {int subscript = stage; // init stage == 0.stage = stage + 1;// Invoke the requested Valve for the current request threadif (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("No valve");}}} // end of inner class

【5.3】SimpleWrapper类
1)该类实现了Wrapper接口:并提供了 allocate 和 load 方法的实现;
2)getLoader()方法:该方法返回一个用于载入servlet 类的载入器。若Wrapper实例已经关联了一个载入器,则直接将其返回;否则,它将返回父容器的载入器。若没有父容器,getLoader方法会返回null;
public Servlet allocate() throws ServletException {// Load and initialize our instance if necessaryif (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 usedif (loader==null) {throw new ServletException("No loader.");}ClassLoader classLoader = loader.getClassLoader();// Load the specified servlet class from the appropriate class loaderClass 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 itselftry {servlet = (Servlet) classClass.newInstance();}catch (Throwable e) {throw new ServletException("Failed to instantiate servlet");}// Call the initialization method of this servlettry {servlet.init(null);}catch (Throwable f) {throw new ServletException("Failed initialize servlet.");}return servlet;}

3)SimpleWrapper类有一个Pipeline实例:并该为Pipeline实例设置基础阀;

【5.4】SimpleWrapperValve类
1)SimpleWrapperValve是一个基础阀:用于处理对SimpleWrapper类的请求,其最主要的方法是 invoke方法;
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 requesttry {servlet = wrapper.allocate();if (hres!=null && hreq!=null) {servlet.service(hreq, hres);}else {servlet.service(sreq, sres);}}catch (ServletException e) {}  }

【5.5】ClientIPLoggerValve类
1)ClientIPLoggerValve类所表示的阀:用来将client的IP 地址输出到控制台上;
2)注意其invoke方法:它先调用方法参数 valveContext的 invokeNext方法来调用管道中的下一个阀。然后,它会把几行字符串output到 console;
【5.6】HeaderLoggerValve类
1)HeaderLoggerValve类作用:会把请求头信息output到 console;
2)注意其invoke方法:它先调用方法参数 valveContext的 invokeNext方法来调用管道中的下一个阀。
【5.7】Bootstrap1
step1)创建 HttpConnector 和 SimpleWrapper实例,并将需要加载的 servlet name 赋值给 Wrapper实例;
step2)创建一个载入器和两个阀,将载入器设置到Wrapper实例中 ;
step3)将上述创建的两个阀添加到 Wrapper的管道中;
step4)将Wrapper 实例设置为 连接器的servlet容器,并初始化并启动连接器
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("servlet.ModernServlet"); // 设置servlet的相对路径Loader loader = new SimpleLoader(); // 类加载器Valve valve1 = new HeaderLoggerValve(); // 把请求头信息output到 consoleValve valve2 = new ClientIPLoggerValve();// 用来将client的IP 地址输出到控制台上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();}  }}

Attention)我这里总结了该测试用例的调用流程图(共3张)
【5.8】运行应用程序
1)运行参数
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomcat.chapter5.startup/B ootstrap1
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
ModernServlet -- init
Client IP Logger Valve
127.0.0.1
------------------------------------
Header Logger Valve
host:localhost:8080
connection:keep-alive
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
accept-encoding:gzip, deflate, sdch
accept-language:zh-CN,zh;q=0.8,en;q=0.6
------------------------------------
Client IP Logger Valve
127.0.0.1
------------------------------------
Header Logger Valve
host:localhost:8080
connection:keep-alive
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
accept-encoding:gzip, deflate, sdch
accept-language:zh-CN,zh;q=0.8,en;q=0.6
------------------------------------

2)运行结果


【6】Context应用程序
0)intro to Context app:本app 展示了如何使用一个包含了两个Wrapper实例的Context实例来构建web app, 这两个Wrapper 实例包装了两个servlet类,当应用程序有多个 Wrapper实例时,需要使用一个 映射器。映射器是组件,帮助servlet容器(Context实例)选择一个子容器来处理某个指定的请求;
1)虽然有些应用程序只需要一个servlet,但大部分web app 是需要多个servlet合作的。这些应用程序中,需要的servlet容器是Context,不是Wrapper;
2)本应用程序的映射器:是SimpleContextMapper类的实例,该类实现类Mapper接口,servlet容器可以使用多个 映射器来支持不同的协议。
public interface Mapper {public Container getContainer(); // 返回与该映射器相关联的servlet容器的实例;public void setContainer(Container container); // 设置与该映射器相关联的servlet容器;public String getProtocol(); // 返回该映射器负责处理的协议public void setProtocol(String protocol);    //指定该映射器负责处理哪种协议public Container map(Request request, boolean update); // 返回要处理某个特定请求的子容器的实例;
}
3)SimpleContext类:是Context容器的一个实例,它使用了SimpleContextMapper 类的实例作为其映射器,将SimpleContextValve 的实例作为基础阀;(干货——SimpleContextValve 的实例作为基础阀,基础阀非常重要)
4)Context容器中额外添加了两个阀:ClinetIPLoggerValve 和 HeaderLoggerValve,并包含两个 Wrapper 实例作为其子容器,二者都是 SimpleWrapper 实例;这两个Wrapper实例使用 SimpleWrapperValve 实例作为其基础阀,不再添加其他阀;(干货——非基础阀,如何在管道中遍历基础阀和非基础阀遍历,我想我已经说的狗明白了,不明白的朋友,看本文章节【2】的管道任务的文字描述)
5)剩下的内容包括:
step1)容器包含一个管道,容器的invoke方法会调用管道的invoke方法;
step2)管道的invoke方法会调用所有添加到其容器中的阀,然后再调用其基础阀的invoke方法;
step3)在Wrapper实例中, 基础阀负责载入相关联的servlet类,并对请求进行响应;
step4)在包含子容器的 Context实例中, 基础阀使用映射器来查找一个子容器,该子容器负责处理接收到的请求。若找到了相应的子容器,则调用其invoke方法,转到step1继续执行;
6)下面对上述的steps 做 detailed intro
step1)SimpleContext类的invoke方法调用管道的invoke方法:
step2)管道SimplePipeline的invoke如下:
public void invoke(Request request, Response response)throws IOException, ServletException {// Invoke the first Valve in this pipeline for this request(new SimplePipelineValveContext()).invokeNext(request, response); // 会调用所有添加到Context 实例中的阀,然后再调用基础阀的invoke方法;}
step3)SimpleContext类中,基础阀是 SimpleContextValve类的实例。在SimpleContextValve类的 invoke方法中, SimpleContextValve实例使用了 Context实例的映射器来查找 Wrapper容器;  
public class SimpleContextValve implements Valve, Contained {protected Container container;public void invoke(Request request, Response response, ValveContext valveContext)throws IOException, ServletException {// Validate the request and response object typesif (!(request.getRequest() instanceof HttpServletRequest) ||!(response.getResponse() instanceof HttpServletResponse)) {return;     // NOTE - Not much else we can do generically}// Disallow any direct access to resources under WEB-INF or META-INFHttpServletRequest 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 RequestWrapper wrapper = null;try {wrapper = (Wrapper) context.map(request, true); // the core line, <span style="font-family: SimSun; font-size: 18px; line-height: 24px;">使用了 Context实例的映射器来查找 Wrapper容器</span>}
 public Container map(Request request, boolean update) {  // SimpleContextMapper.map()// Identify the context-relative URI to be mappedString 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 specificationWrapper wrapper = null;String servletPath = relativeURI;String pathInfo = null;String name = context.findServletMapping(relativeURI); // this lineif (name != null)wrapper = (Wrapper) context.findChild(name); // and this linereturn (wrapper);}
}
public String findServletMapping(String pattern) { // SimpleContext.findServletMapping()synchronized (servletMappings) {return ((String) servletMappings.get(pattern));// protected HashMap servletMappings = new HashMap();}}
public Container findChild(String name) { // SimpleContext.findChild()if (name == null)return (null);synchronized (children) {       // Required by post-start changesreturn ((Container) children.get(name));// protected HashMap children = new HashMap();}}
Attention)
A1)Wrapper实例的管道会调用 SimpleWrapperValve类的 invoke方法,它会分配servlet实例,并调用其 service方法;
A2)Wrapper实例中:并没有与载入器相关联,但是Context 实例关联了类载入器,因此,SimpleWrapper类的 getLoader() 方法会返回父容器的载入器;
【6.1】SimpleContextValve类
1)该类是 SimleContext的基础阀,最重要的方法是invoke方法;
【6.2】SimpleContextMapper类
 
1)map方法需要两个参数:一个request对象和一个布尔变量。
2)在本app中, 忽略了第2个参数。map() 方法会从request对象中解析出请求的上下文路径,并调用 Conetext 实例的findServletMapping() 方法 来获取一个与该路径相关联的名称,如果找到了这个名称,则它调用 Context实例的findChild方法获取一个 Wrapper 实例;
【6.3】SimpleContext类
1)intro to SimpleContext:该类是 Context容器 的实例,是与连接器相关联的主容器;
2)本应用程序有两种URL模式:用来调用两个 Wrapper实例,如/Primitive 和 /Modern 模式;当然,也可以将多个 URL模式映射到一个Wrapper实例上。只需要添加这些模式即可;
3)SimpleContext类必须实现 Container 和 Context接口,实现的方法包括以下几个(methods):
method1)addServletMapping():添加一个 URL模式 / Wrapper实例的名称对;通过给定的名称添加用于调用Wrapper实例的每种模式;
method2)findServletMapping():通过URL模式 查找对应的Wrapper 实例名称;该方法用来查找某个特殊URL 模式对应的Wrapper实例;
method3)addMapper():在Context容器中添加一个映射器。SimpleContext类声明有两个变量: mapper and mappers 。mapper表示程序使用的默认映射器,mappers包含SimpleContext 实例中所有可用的映射器。第一个被添加到 Context容器中的映射器称为默认映射器;
method4)findMapper():找到正确的映射器,在 SimpleContext类中,它返回默认映射器;
method5)map():返回负责处理当前请求的 Wrapper实例;
public class SimpleContext implements Context, Pipeline {public SimpleContext() {pipeline.setBasic(new SimpleContextValve());}protected HashMap children = new HashMap();protected Loader loader = null;protected SimplePipeline pipeline = new SimplePipeline(this);protected HashMap servletMappings = new HashMap();protected Mapper mapper = null;protected HashMap mappers = new HashMap();private Container parent = null;public void addServletMapping(String pattern, String name) {synchronized (servletMappings) {servletMappings.put(pattern, name);}}public String findServletMapping(String pattern) {synchronized (servletMappings) {return ((String) servletMappings.get(pattern));}}public Loader getLoader() {if (loader != null)return (loader);if (parent != null)return (parent.getLoader());return (null);}public void addChild(Container child) {child.setParent((Container) this);children.put(child.getName(), child);}public void addMapper(Mapper mapper) {// this method is adopted from addMapper in ContainerBase// the first mapper added becomes the default mappermapper.setContainer((Container) this);      // May throw IAEthis.mapper = mapper;synchronized(mappers) {if (mappers.get(mapper.getProtocol()) != null)throw new IllegalArgumentException("addMapper:  Protocol '" +mapper.getProtocol() + "' is not unique");mapper.setContainer((Container) this);      // May throw IAEmappers.put(mapper.getProtocol(), mapper);if (mappers.size() == 1)this.mapper = mapper;elsethis.mapper = null;}}public Container findChild(String name) {if (name == null)return (null);synchronized (children) {       // Required by post-start changesreturn ((Container) children.get(name));}}public Container[] findChildren() {synchronized (children) {Container results[] = new Container[children.size()];return ((Container[]) children.values().toArray(results));}}public ContainerListener[] findContainerListeners() {return null;}public Mapper findMapper(String protocol) {// the default mapper will always be returned, if any,// regardless the value of protocolif (mapper != null)return (mapper);elsesynchronized (mappers) {return ((Mapper) mappers.get(protocol));}}public void invoke(Request request, Response response)throws IOException, ServletException {pipeline.invoke(request, response);}public Container map(Request request, boolean update) {//this method is taken from the map method in org.apache.cataline.core.ContainerBase//the findMapper method always returns the default mapper, if any, regardless the//request's protocolMapper mapper = findMapper(request.getRequest().getProtocol());if (mapper == null)return (null);// Use this Mapper to perform this mappingreturn (mapper.map(request, update));}
【6.4】BootStrap2
step1)首先实例化Tomcat的默认连接器,创建两个Wrapper实例,并指定名称。  
step2)main() 方法创建一个 SimpleContext实例,并将 wrapper1 和 wrapper2 作为子容器添加到 SimpleContext 实例中。此外,它还会实例化两个阀:ClientIPLoggerValve 和 HeaderLoggerValve,并将它们添加到 SimpleContext实例中:
step3)接下来,它会从SimpleMapper类创建一个映射器对象,将其添加到 SimpleContext 实例中。映射器负责查找Context 实例中的子容器来处理 HTTP请求
step4)要载入servlet类,还需要一个载入器。并将其添加到 Context实例中。Wrapper实例可以通过 其 getLoader方法来获取载入器,因为Wrapper实例是 Context实例的子容器:
step5)添加servlet映射。为 两个Wrapper 实例添加两种模式:
step6)将Context容器与 连接器相关联,并初始化连接器,调用其 start方法;
public final class Bootstrap2 { public static void main(String[] args) {HttpConnector connector = new HttpConnector();    Wrapper wrapper1 = new SimpleWrapper();  // 最低级的servlet容器 Wrapper,利用加载器动态加载和封装servletwrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet");Context context = new SimpleContext(); // 比Wrapper高一级的容器 Context,可以context.addChild(wrapper1);context.addChild(wrapper2);Valve valve1 = new HeaderLoggerValve(); // 非基础阀Valve valve2 = new ClientIPLoggerValve(); // 非基础阀((Pipeline) context).addValve(valve1);((Pipeline) context).addValve(valve2); // 添加基础阀到 ContextMapper mapper = new SimpleContextMapper(); // 映射器mapper.setProtocol("http");context.addMapper(mapper); // 将映射器添加到 contextLoader loader = new SimpleLoader(); // 类加载器context.setLoader(loader); // 将该类加载器设置到context容器中// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive"); // 添加servlet访问路径映射(put到HashMap中)context.addServletMapping("/Modern", "Modern");connector.setContainer(context); // 设置容器到Tomcat 连接器try {connector.initialize();connector.start();// make the application wait until we press a key.System.in.read();}catch (Exception e) {e.printStackTrace();}}
}
Attention)我这里还是总结了一张 Context应用程序的流程图(本图跨度比较大,请结合上述的3张流程图的调用过程来看)
protected HashMap children = new HashMap(); // servlet资源名称 和 具体servlet资源URI(类加载器加载路径) 的映射集合。
protected HashMap servletMappings = new HashMap();// 访问路径和servlet资源名称的映射集合protected HashMap mappers = new HashMap(); // 映射器集合(一个Context容器可以有多个映射器)

以上代码的调用steps:(client发出HTTP请求(请求资源的URI,如 http://localhost:8080/Modern  )后,server的处理过程)

step1)HttpConnector类的大致工作:
step1.1)initialize方法:调用该类的open方法创建服务器套接字;
step1.2)start方法:开启一个线程,该线程中有一个while循环,不断接收client发送的HTTP连接请求,接着调用其类的createProcessor方法;
step1.3)createProcessor方法:调用其类的 newProcessor方法;
step1.4)newProcessor方法:创建HttpProcessor(HTTP连接器的支持类,HTTP请求处理器),利用HttpProcessor实例开启一个线程,调用 HttpProcessor.run()方法;(转向HttpProcessor类的run方法)(干货中的干货——也即当clients 发出http 连接请求后,HttpConnector 在while循环中创建HttpConnector的支持类 HttpProcessor,Http处理器类,并调用该类的start方法,开启线程,即while循环为每一个client 请求 开启一个线程进行处理,多么巧妙)

step2)HttpProcessor类的大致工作:
step2.1)run方法:传入套接字参数,并调用process方法;
step2.2)process方法:依次调用 parseConnection()方法, parseRequest()方法, parseHeader() 方法;调用上述三个方法后,会调用连接器HttpConnector实例的关联容器的invoke方法;(转向container的invoke方法)注意:本程序的container是SimpleContext类,下面转向SimpleContext.invoke() 方法;
step3)SimpleContext.invoke() 方法(注意SimpleContext中的构造器设置了其关联管道的基础阀是SimpleContextValve,基础阀非常重要)调用 pipeline.invoke方法;
step3.1)pipeline.invoke()方法:会遍历非基础阀(非基础阀集合就是一个阀数组),直到遍历到基础阀(SimpleContextValve),调用基础阀SimpleContextValve 的 invoke方法;
step3.2)SimpleContextValve.invoke()方法:调用map 查询到请求URI对应的Wrapper并返回,返回SimpleWrapper,并调用SimpleWrapper.invoke方法;

step4)SimpleWrapper.invoke()方法(注意SimpleWrapper中的构造器设置了其关联管道的基础阀是SimpleWrapperValve,基础阀非常重要)
step4.1)pipeline.invoke()方法:会遍历非基础阀(SimpleWrapper关联管道中根本就没有非基础阀,只有基础阀SimpleWrapperValve),直到遍历到基础阀(SimpleContextValve),调用基础阀SimpleWrapperValve 的 invoke方法;
step4.2)非基础阀SimpleWrapperValve.invoke方法:调用其关联wrapper(SimpleWrapper)的allocate方法,该方法就是返回请求的servlet实例,返回实例后,调用该servlet实例的 service方法;

SimpleWrapper.allocate方法的补充)该方法先创建一个类加载器,然后根据HttpProcessor.parseRequest 解析出的 URI 来加载对应的 servlet实例,并返回该实例;

为什么我说Container容器是这个类,Wrapper容器是那个类?因为启动程序Bootstrap2.main 方法中对变量的定义已经把它们的关系说的很明白了,在接下来的【6.5】章节我再次po出 Bootstrap2.java 的源代码。
Attention)关于基础阀
A1)基础阀是非常重要的东西,是管道遍历阀(基础阀和非基础阀)的终点;
A2)需要区分的是:SimpleContext设置其关联管道的基础阀是 SimpleContextValve,而SimpleWrapper设置其关联管道的基础阀是 SimpleWrapperValve,不要一根筋的认为所有容器关联的管道的基础阀都是一样的,定要万分留意;
补充)调用map 查询到请求URI对应的Wrapper(在映射器中查找与URI对应的 Wrapper的steps):
step1)SimpleContextMapper.map方法: 先调用context.findServletMapping方法,传入parseRequest解析出的请求路径中的URI 到 servletMappings查找该URI对应的servlet名称;
step2)查找到servlet名称后,调用context.findChild()方法,传入servlet名称 到 children中查找对应的Wrapper容器,要知道Wrapper容器封装了具体的servlet容器(Wrapper);

【6.5】运行应用程序
0)Bootstrap2.java 源代码
public final class Bootstrap2 {public static void main(String[] args) {HttpConnector connector = new HttpConnector();Wrapper wrapper1 = new SimpleWrapper();  // 最低级的servlet容器 Wrapper,利用加载器动态加载和封装servletwrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet");Context context = new SimpleContext(); // 比Wrapper高一级的容器 Context,可以context.addChild(wrapper1);context.addChild(wrapper2);Valve valve1 = new HeaderLoggerValve(); // 非基础阀Valve valve2 = new ClientIPLoggerValve(); // 非基础阀((Pipeline) context).addValve(valve1);((Pipeline) context).addValve(valve2); // 添加非基础阀到 ContextMapper mapper = new SimpleContextMapper(); // 映射器mapper.setProtocol("http");context.addMapper(mapper); // 将映射器添加到 contextLoader loader = new SimpleLoader(); // 类加载器context.setLoader(loader); // 将该类加载器设置到context容器中// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive"); // 添加servlet访问路径映射(put到HashMap中)context.addServletMapping("/Modern", "Modern");connector.setContainer(context); // 设置容器到Tomcat 连接器try {connector.initialize();connector.start();// make the application wait until we press a key.System.in.read();}catch (Exception e) {e.printStackTrace();}}
}
1)运行参数
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot  com.tomcat.chapter5.startup.
Bootstrap2
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
Client IP Logger Valve
127.0.0.1
------------------------------------
Header Logger Valve
host:localhost:8080
connection:keep-alive
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
accept-encoding:gzip, deflate, sdch
accept-language:zh-CN,zh;q=0.8,en;q=0.6
------------------------------------
init
from service
Client IP Logger Valve
127.0.0.1
------------------------------------
2)运行结果


tomcat(5)servlet容器(lastest version)相关推荐

  1. tomcat(5)servlet容器

    [0]README 0.0)本文部分文字描述转自:"深入剖析tomcat",旨在学习 tomcat(5)servlet容器 的基础知识: 0.1)intro to servlet容 ...

  2. SpringBoot之配置嵌入式Servlet容器

    1.概述 文章目录 1.概述 2.如何修改SpringBoot的默认配置 3.定制和修改Servlet容器的相关配置 4.注册Servlet三大组件 5.替换为其他嵌入式Servlet容器 6.嵌入式 ...

  3. Spring Boot切换其他嵌入式的Servlet容器

    Spring Boot默认支持: Tomcat(默认使用) <dependency><groupId>org.springframework.boot</groupId& ...

  4. Spring boot切换Servlet容器

    切换Servlet容器 Spring boot默认配置Tomcat作为Servlet容器 引入web模块,默认使用嵌入式的Tomcat 可以切换Jetty.Undertow 默认配置 Pom文件,查看 ...

  5. servlet容器_Tomcat 容器与servlet的交互原理

    点击蓝字"程序员考拉"欢迎关注! Tomcat 是Web应用服务器,是一个Servlet/JSP容器. Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Serv ...

  6. tomcat和servlet的关系

    tomcat和servlet的关系 Tomcat 是Web应用服务器,是一个Servlet/JSP容器. Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Ser ...

  7. spring boot没有web.xml,如何向嵌入式的servlet容器中注册servlet组件

    1. Spring boot默认使用Tomcat作为嵌入式的servlet容器,只要引入spring-boot-starter-web依赖,就会默认用Tomcat作为servlet容器. 2. Spr ...

  8. tomcat和servlet的关系及区别

    tomcat和servlet的关系及区别 Tomcat 是Web应用服务器,是一个Servlet/JSP容器. Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将 ...

  9. springboot-嵌入式Servlet容器(Tomcat)源码分析以及容器切换

    目录 一.springboot的嵌入式Servlet容器(tomcat) 1.原理 2.源码 (1)ServletWebServerApplicationContext的createWebServer ...

最新文章

  1. HDOJ HDU 2080 夹角有多大II ACM 2080 IN HDU
  2. 神经网络收敛标准与准确率之间的数学关系
  3. Qt中qDebug()技巧初探
  4. 李彦宏:AI在我有生之年不会毁灭人类
  5. 李航第一章课后习题答案
  6. opencv:图像的基本变换
  7. 2021中国家居行业洞察白皮书
  8. hue集成mysql报错_hue集成hive访问报database is locked
  9. 我为啥要当程序员未来规划漫谈
  10. python面向对象——类(上)
  11. vmware安装报错及注册时无权输入许可证密钥的解决办法及步骤
  12. 自带浏览器打不开网页?
  13. “扣哒杯” AI世青赛公布2021-2022年度全国决赛个人获奖名单
  14. CSS特效--图像悬停效果
  15. 面试通过了,也给了Offer,不去有什么后果?
  16. 杭电多校联赛2017年总结
  17. 【深度学习】云服务器推荐及教程
  18. NOIP 2018 滚粗记(bushi)
  19. Spark 的共享变量之累加器和广播变量
  20. 【JAVA】计算算式

热门文章

  1. test120200612shu
  2. Vue整合SpringBoot项目实战之Vue+Element-Ui搭建前端项目
  3. python检查交换机端口状态_Python3 自动登录全部交换机查询MAC所在端口
  4. 图像处理系列05——Jetson Xavier NX平台JPEG硬解
  5. Android 开发 TCP协议
  6. java-php-python-ssm校园面包超市系统计算机毕业设计
  7. Unity功能—— 在VS中快速访问Unity API对应文档
  8. 西北农林科技大学linux实验报告,西北农林科技大学 linux考试复习提纲
  9. EO、DTO、ViewModel的区别
  10. (一)kibana使用