购物网站建设源码,最新的电商资讯有哪些,深圳手机网站建设牛商网,白和黑人做网站【0】README0.0#xff09;本文部分文字描述转自 “how tomcat works”#xff0c;旨在学习 “tomcat(11)StandardWrapper源码剖析” 的基础知识#xff1b;0.1#xff09;StandardWrapper 是 Catalina中对Wrapper接口的标准实现#xff1b;要知道#xff0c;tomcat 中有…【0】README0.0本文部分文字描述转自 “how tomcat works”旨在学习 “tomcat(11)StandardWrapper源码剖析” 的基础知识0.1StandardWrapper 是 Catalina中对Wrapper接口的标准实现要知道tomcat 中有4种类型的容器EngineHostContext 和 Wrapper干货——review tomcat 中有4种类型的容器EngineHostContext 和 Wrapper【1】方法调用序列
1对于每个引入的http 请求连接器都会调用与其关联的servlet容器的 invoke() 方法。然后servlet容器会调用其所有子容器的invoke() 方法2下图展示了连接器接收到http 请求后的方法调用的协作图3上图的具体steps 如下step1连接器创建 request 和 response对象 step2连接器调用StandardContext.invoke()方法 step3StandardContext.invoke()方法调用其管道的invoke() 方法。StandardContext的管道对象的基础阀是 StandardCoantextValve类的实例因此 StandardContext 的管道会调用 StandardContextValve.invoke()方法 step4StandardContextValve.invoke()方法 获取相应的Wrapper 实例处理 http请求调用Wrapper实例的invoke()方法 step5StandardWrapper类是Wrapper接口的标准实现StandardWrapper.invoke()方法 会调用其管道对象的invoke()方法 step6StandardWrapper的管道对象中的基础阀是 StandardWrapperValve 类的实例因此会调用StandardWrapperValve.invoke()方法StandardWrapperValve.invoke()方法会调用Wrapper实例的 allocate() 方法获取servlet实例 step7allocate()方法调用load() 方法载入相应的servlet类若已经载入则无需重复载入 step8load()方法调用servlet实例的init()方法 step9StandardWrapperValve调用servlet.service()方法 // Call the filter chain for this request// NOTE: This also calls the servlets service() methodtry { // org.apache.catalina.core.StandardWrapperValve.invoke()String jspFile wrapper.getJspFile();if (jspFile ! null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet ! null) (filterChain ! null)) {filterChain.doFilter(sreq, sres); // highlight line. doFilter() calls servlet.service()}sreq.removeAttribute(Globals.JSP_FILE_ATTR);} AttentionStandardContext类的构造函数会设置StandardContextValve类的一个实例作为其基础阀 public StandardContext() { // org.apache.catalina.core.StardardContextsuper();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);
}AttentionStandardWrapper类的构造函数也会设置一个 StandardWrapperValve实例作为其基础阀public StandardWrapper() { // org.apache.catalina.core.StardardWrappersuper();swValvenew StandardWrapperValve();pipeline.setBasic(swValve);
}4依据上述3小节中的 “处理http 请求的方法调用协作图”本文按照惯例给出了具体的调用过程如下 4.1本文第一张是借用了 “tomcat(10)安全性中章节【6.4】中Supplement-补充模块”的第2张图for spec infoplease visit tomcat(10)安全性这旨在说明从HttpConnector - StandardContext.invoke() - StandardPipeline.invoke()的调用过程4.2本文接着上面的调用过程继续分析调用过程如下图旨在说明 StandardPipeline.invoke() - StandardContextValve.invoke() - StandardWrapper.invoke() - StandardPipeline.invoke() - StandardWrapperValve.invoke() - ApplicationFilterChain().doFilter() - ApplicationFilterChain().internalDoFilter() - HttpServlet(ModernServlet).service() - ModernServlet-doGet() 的调用过程.Bingo对上述协作图和详细调用过程图的分析AnalysisA0要知道Tomcat中有4种容器EngineHostContext 和 Wrapper干货——本文一直强调这一点理解容器的层次结构对于理解tomcat非常重要 A1StandardContext 和 StandardWrapper 都是容器他们都继承自 ContainerBase只不过StandardWrapper是StandardContext的子容器而StandardWrapper是最小的容器即它没有子容器 A2下面分别看StandardWrapperStandardContext的构造函数 和 ContainerBase 的变量定义 public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper {public StandardWrapper() {super();swValvenew StandardWrapperValve();pipeline.setBasic(swValve);}
}
public class StandardContext extends ContainerBase implements Context {public StandardContext() { super();pipeline.setBasic(new StandardContextValve());namingResources.setContainer(this);}
}
public abstract class ContainerBase implements Container, Lifecycle, Pipeline {protected Pipeline pipeline new StandardPipeline(this); // highlight line.protected HashMap children new HashMap();protected int debug 0; protected LifecycleSupport lifecycle new LifecycleSupport(this); protected ArrayList listeners new ArrayList(); protected Loader loader null; protected Logger logger null; protected Manager manager null; protected Cluster cluster null; protected Mapper mapper null; protected HashMap mappers new HashMap(); protected String mapperClass null; protected String name null; protected Container parent null; protected ClassLoader parentClassLoader null; protected Pipeline pipeline new StandardPipeline(this); protected Realm realm null; protected DirContext resources null; protected static StringManager sm StringManager.getManager(Constants.Package); protected boolean started false; protected PropertyChangeSupport support new PropertyChangeSupport(this);
} A3可以看到 父容器ContainerBase定义了管道StandardPipeline而子容器StandardContext 设置StandardContextValve为基础阀而最小的容器StandardWrapper设置StandardWrapperValve为基础阀 A4也即 StandardContext 和 StandardWrapper 共用同一个管道分别设置不同的基础阀当然可以分别设置非基础阀非基础阀在基础阀被调用之前调用 【2】SingleThreadModel已经被弃用了1introservlet类可以实现 javax.servlet.SingleThreadModel 接口这样的servlet类也称为 SingleThreadModelSTMservlet类。根据servlet规范实现此接口的目的是保证 servlet实例一次只处理一个请求Attention若 servlet类实现 SingleThreadModel接口则可以保证绝不会有两个线程同时执行该servlet.service()方法。这一点由 servlet容器通过控制对单一 servlet实例的同步访问实现或者维护一个 servlet实例池然后将每个新请求分派给一个空闲的servlet实例。该接口并不能防止servlet访问共享资源造成的同步问题例如访问类的静态变量或访问servlet作用域之外的类干货——有很多程序员哥哥没有读懂这段话想当然的认为实现了该接口的servlet就是线程安全的。这种想法是错误的请再度一遍上面的引文内容原文作者说的哈哈2事实上实现了 SingleThreadModel 接口的servlet类只能保证在同一时刻只有一个线程在执行该 servlet实例的service()方法。但为了提高执行 性能servlet容器会创建多个STM servlet实例。也就是说STM servlet.service()方法 会在多个STM servlet实例中并发执行。如果servlet实例需要静态类变量或类外的某些资源的话就有可能引起同步问题Atttention在servlet 2.4中SingleThreadModel接口已经被弃用了因为它会使 servlet程序员误以为该接口的servlet类就是多线程安全的【3】StandardWrapper1intro to StandardWrapper其主要任务是 载入它所代表的servlet类并进行实例化2StandardWrapper并不调用servlet的service方法该任务由 StandardWrapperValve对象StandardWrapper实例的管道对象中的基础阀完成3StandardWrapperValve对象通过调用allocate()方法从 StandardWrapper实例中获取servlet实例在获得servlet实例后StandardWrapperValve实例就会调用servlet实例的service()方法【3.1】分配servlet实例 1分配servlet实例是由 StandardWrapper.allocate()方法来完成的allocate方法返回请求的servelt实例2allocate()方法分为两部分partsp1第一部分 allocate()首先检查 instance是否为null若是 则allocate()方法调用 loadServlet()方法载入相关的servlet类然后 整型变量countAllocated加1返回instance的值 p2第二部分 p2.1若StandardWrapper表示的servlet是一个STM servlet类则allocate()会试图从对象池中返回一个servlet实例。变量 instancePool 是一个 java.util.Stack类型的栈其中保存了所有的STM servlet实例 private Stack instancePool null; p2.2只要STM servlet实例数不超过指定的最大值allocate()方法会返回一个 STM servlet实例。整型变量maxInstances 保存了在栈中存储的 STM servlet实例的最大值default value 20 private int maxInstances 20; p2.3而 nInstances 保存了当前 STM servlet实例的数量初始为0 3源码如下 public Servlet allocate() throws ServletException { //org.apache.catalina.core.StandardWrapper.allocate()
// part 1 begins.if (debug 1) log(Allocating an instance);// If we are currently unloading this servlet, throw an exceptionif (unloading)throw new ServletException(sm.getString(standardWrapper.unloading, getName()));// If not SingleThreadedModel, return the same instance every timeif (!singleThreadModel) {// Load and initialize our instance if necessaryif (instance null) {synchronized (this) {if (instance null) {try {instance loadServlet();} catch (ServletException e) {throw e;} catch (Throwable e) {throw new ServletException(sm.getString(standardWrapper.allocate), e);}}}}if (!singleThreadModel) {if (debug 2)log( Returning non-STM instance);countAllocated;return (instance);}} // part1 ends.
// part2 starts.synchronized (instancePool) {while (countAllocated nInstances) {// Allocate a new instance if possible, or else waitif (nInstances maxInstances) {try {instancePool.push(loadServlet());nInstances;} catch (ServletException e) {throw e;} catch (Throwable e) {throw new ServletException(sm.getString(standardWrapper.allocate), e);}} else {try {instancePool.wait();} catch (InterruptedException e) {;}}}if (debug 2)log( Returning allocated STM instance);countAllocated;return (Servlet) instancePool.pop();}}// part2 ends. 【3.2】载入servlet类1StandardWrapper类实现了Wrapper接口的 load() 方法load() 方法调用loadServlet()方法载入某个servlet类并调用其 init() 方法此时要传入一个 javax.servlet.ServletConfig实例作为参数2loadServlet() 方法是如何工作的public synchronized void load() throws ServletException { // org.apache.catalina.core.StandardWrapper.load()instance loadServlet();
}public synchronized Servlet loadServlet() throws ServletException { // org.apache.catalina.core.StandardWrapper.loadServlet()// Nothing to do if we already have an instance or an instance poolif (!singleThreadModel (instance ! null))return instance;PrintStream out System.out;if (swallowOutput) {SystemLogHandler.startCapture();}Servlet servlet null;try {// If this servlet is really a JSP file, get the right class.// HOLD YOUR NOSE - this is a kludge that avoids having to do special// case Catalina-specific code in Jasper - it also requires that the// servlet path be replaced by the jsp-file element content in// order to be completely effectiveString actualClass servletClass;if ((actualClass null) (jspFile ! null)) {Wrapper jspWrapper (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);if (jspWrapper ! null)actualClass jspWrapper.getServletClass();}// Complain if no servlet class has been specifiedif (actualClass null) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.notClass, getName()));} // Acquire an instance of the class loader to be usedLoader loader getLoader();if (loader null) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.missingLoader, getName()));} ClassLoader classLoader loader.getClassLoader(); // Special case class loader for a container provided servletif (isContainerProvidedServlet(actualClass)) {classLoader this.getClass().getClassLoader();log(sm.getString(standardWrapper.containerServlet, getName()));} // Load the specified servlet class from the appropriate class loaderClass classClass null;try {if (classLoader ! null) {classClass classLoader.loadClass(actualClass);} else {classClass Class.forName(actualClass);}} catch (ClassNotFoundException e) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.missingClass, actualClass),e);}if (classClass null) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.missingClass, actualClass));} // Instantiate and initialize an instance of the servlet class itselftry {servlet (Servlet) classClass.newInstance();} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString(standardWrapper.notServlet, actualClass), e);} catch (Throwable e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString(standardWrapper.instantiate, actualClass), e);} // Check if loading the servlet in this web application should be// allowedif (!isServletAllowed(servlet)) {throw new SecurityException(sm.getString(standardWrapper.privilegedServlet,actualClass));} // Special handling for ContainerServlet instancesif ((servlet instanceof ContainerServlet) isContainerProvidedServlet(actualClass)) {((ContainerServlet) servlet).setWrapper(this);} // Call the initialization method of this servlettry {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);servlet.init(facade);// Invoke jspInit on JSP pagesif ((loadOnStartup 0) (jspFile ! null)) {// Invoking jspInitHttpRequestBase req new HttpRequestBase();HttpResponseBase res new HttpResponseBase();req.setServletPath(jspFile);req.setQueryString(jsp_precompiletrue);servlet.service(req, res);}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);} catch (UnavailableException f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);unavailable(f);throw f;} catch (ServletException f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);// If the servlet wanted to be unavailable it would have// said so, so do not call unavailable(null).throw f;} catch (Throwable f) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet, f);// If the servlet wanted to be unavailable it would have// said so, so do not call unavailable(null).throw new ServletException(sm.getString(standardWrapper.initException, getName()), f);} // Register our newly initialized instancesingleThreadModel servlet instanceof SingleThreadModel;if (singleThreadModel) {if (instancePool null)instancePool new Stack();}fireContainerEvent(load, this);} finally {if (swallowOutput) {String log SystemLogHandler.stopCapture();if (log ! null log.length() 0) {if (getServletContext() ! null) {getServletContext().log(log);} else {out.println(log);}}}}return servlet;} step1检查当前的StandardWrapper类是否表示的是一个 STM servlet类若不是且变量instance不为null表示以前已经载入过这个servlet它就直接返回该实例 // Nothing to do if we already have an instance or an instance poolif (!singleThreadModel (instance ! null))return instance; step2获得 System.out 和 System.err 的输出便于它使用 javax.servlet.ServletConfig.log() 方法记录日志消息 PrintStream out System.out;if (swallowOutput) {SystemLogHandler.startCapture();} step3定义类型为javax.servlet.Servlet 名为servlet 的变量其表示已载入的servlet实例会由 loadServlet()方法返回 Servlet servlet null; step4由于Catalina是一个JSP容器故loadServlet()方法必须检查请求的servlet是不是一个jsp 页面。若是则loadServlet() 方法需要获取代表该jsp 页面的实际servlet类 String actualClass servletClass;if ((actualClass null) (jspFile ! null)) {Wrapper jspWrapper (Wrapper)((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);if (jspWrapper ! null)actualClass jspWrapper.getServletClass();
}
// public static final String JSP_SERVLET_NAME jsp;span stylefont-family: SimSun; line-height: 1.5; background-color: inherit; /span step5如果找不到该jsp 页面的servlet类则会使用变量 servletClassactualClass的值。若没有调用StandardWrapper.serServletClass() 方法设置servletClass的值则会抛出异常并停止执行后续方法 // Complain if no servlet class has been specifiedif (actualClass null) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.notClass, getName()));}span stylefont-family: SimSun; background-color: rgb(255, 255, 255); /span step6这时要载入的servlet类名已经解析完了loadServlet()方法会获取载入器 Loader loader getLoader();public Loader getLoader() { // org.apache.catalina.core.ContainerBase.getLoader();if (loader ! null)return (loader);if (parent ! null)return (parent.getLoader());return (null);} step7若找到载入器loader则loadServlet()方法调用getClassLoader()方法获取一个ClassLoader ClassLoader classLoader loader.getClassLoader(); step8Catalina提供了一些用于访问servlet容器内部数据的专用servlet类。如果某个servlet类是这种专用的servlet即若isContainerProvidedServlet()方法返回true则变量 classLoader被赋值为另一种ClassLoader实例如此一来这个servlet实例就可以访问Catalina的内部数据了 // Special case class loader for a container provided servletif (isContainerProvidedServlet(actualClass)) {classLoader this.getClass().getClassLoader();log(sm.getString(standardWrapper.containerServlet, getName()));} step9准备好类载入器和准备载入的servlet类名后loadServlet()方法就可以载入servlet类了 // Load the specified servlet class from the appropriate class loaderClass classClass null;try {if (classLoader ! null) {classClass classLoader.loadClass(actualClass);} else {classClass Class.forName(actualClass);}} catch (ClassNotFoundException e) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.missingClass, actualClass),e);}
if (classClass null) {unavailable(null);throw new ServletException(sm.getString(standardWrapper.missingClass, actualClass));} step10实例化该servlet // Instantiate and initialize an instance of the servlet class itselftry {servlet (Servlet) classClass.newInstance();} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString(standardWrapper.notServlet, actualClass), e);} catch (Throwable e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString(standardWrapper.instantiate, actualClass), e);}step11在loadServlet()方法实例化这个servlet之前它会调用 isServletAllowed()方法检查该servlet 类是否允许载入 // Check if loading the servlet in this web application should be// allowedif (!isServletAllowed(servlet)) {throw new SecurityException(sm.getString(standardWrapper.privilegedServlet,actualClass));}step12若通过了安全检查它还会继续检查该servlet类是否是一个 ContainerServlet类型的servlet实现了 org.apache.catalina.ContainerServlet接口的 servlet可以访问Catalina的内部功能。若该servlet类是一个 ContainerServletloadServlet()方法会调用 ContainerServlet.setWrapper()传入StandardWrapper实例 // Special handling for ContainerServlet instancesif ((servlet instanceof ContainerServlet) isContainerProvidedServlet(actualClass)) {((ContainerServlet) servlet).setWrapper(this);} step13触发BEFORE_INIT_EVENT事件调用servlet实例的 init()方法init()方法传入了javax.servlet.ServletConfig外观对象 // Call the initialization method of this servlettry {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);servlet.init(facade); // highlight line. step14若变量 loadOnStartup 大于0 且被请求的servlet类实际上是一个jsp 页面则servlet实例的service()方法 if ((loadOnStartup 0) (jspFile ! null)) {// Invoking jspInitHttpRequestBase req new HttpRequestBase();HttpResponseBase res new HttpResponseBase();req.setServletPath(jspFile);req.setQueryString(jsp_precompiletrue);servlet.service(req, res); // highlight line.}instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet); step15触发AFTER_INIT_EVENT事件 instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, step16若StandardWrapper对象表示的servlet类是一个STM servlet则将该servlet实例添加到servlet实例池中。因此会判断 instancePool 是否为null若是则要给他赋值一个Stack 对象 // Register our newly initialized instancesingleThreadModel servlet instanceof SingleThreadModel;if (singleThreadModel) {if (instancePool null)instancePool new Stack(); // highlight line. } step17在finally代码块中loadServlet()方法停止捕获System.out 和 System.err 对象记录在载入 ServletContext.log()方法的过程中产生的日志消息 finally {if (swallowOutput) {String log SystemLogHandler.stopCapture();if (log ! null log.length() 0) {if (getServletContext() ! null) {getServletContext().log(log); // highlight line.} else {out.println(log);}}}}
public ServletContext getServletContext() { org.apache.catalina.core.StandardWrapper.getServletContext()if (parent null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());} step18最后返回已载入的servlet实例 return servlet; 【3.3】ServletConfig对象1intro在上述step13中提到了 servlet.init(facade)而facade 是 javax.servlet.ServletConfig对象的一个外观变量2StandardWrapper对象是如何获取 servletConfig 对象的答案就在 StandardWrapper中该类不仅实现了 Wrapper接口还实现了 javax.servlet.ServletConfig 接口public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper { // org.apache.catalina.core.StandardWrapper
// ......
}
public interface ServletConfig { // javax.servlet.ServletConfig public String getServletName();public ServletContext getServletContext(); public String getInitParameter(String name);public Enumeration getInitParameterNames();
}3javax.servlet.ServletConfig 接口有4个方法getServletContext() , getServletName(), getInitParameter() getInitParameterNames()方法下面对这4个方法进行说明method1getServletConfig()方法 public ServletContext getServletContext() { // org.apache.catalina.core.StandardWrapper.getServletContext()if (parent null)return (null);else if (!(parent instanceof Context))return (null);elsereturn (((Context) parent).getServletContext());}/*** Return the servlet context for which this Context is a facade.*/public ServletContext getServletContext() { // org.apache.catalina.core.StandardContext.getServletContext()if (context null)context new ApplicationContext(getBasePath(), this);return (context);} Attention正如以上代码所展示的那样无法单独使用一个Wrapper实例来表示一个 servlet 类的定义。Wrapper 实例必须驻留在某个 Context 容器中这样当调用其父容器的getServletConfig()方法时才能返回ServletContext类的一个实例 method2getServletName()方法该方法返回 servlet类的名字该方法的签名如下 public String getServletName() { // org.apache.catalina.core.StandardWrapper.getServletName()return (getName());}
public String getName() { // org.apache.catalina.core.ContainerBase.getName().
// 因为 public final class StandardWrapper extends ContainerBasereturn (name);} method3getInitParameter()方法该方法返回指定初始参数的值 public String getInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.getInitParameter()return (findInitParameter(name));}
public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name));}} 对getInitParameter方法的分析Analysis A1在StandardWrapper类中初始化参数 parameters 存储在一个 HashMap类型中 private HashMap parameters new HashMap(); A2通过addInitParameter()方法传入参数的名字 和 对应的值 来填充变量 parameters 的值 public void addInitParameter(String name, String value) { // org.apache.catalina.core.StandardWrapper.addInitParameter().synchronized (parameters) {parameters.put(name, value);}fireContainerEvent(addInitParameter, name); // highlight line.}
public void fireContainerEvent(String type, Object data) {// org.apache.catalina.core.ContainerBase.fireContainerEvent().if (listeners.size() 1)return;ContainerEvent event new ContainerEvent(this, type, data);ContainerListener list[] new ContainerListener[0];synchronized (listeners) {list (ContainerListener[]) listeners.toArray(list);}for (int i 0; i list.length; i)((ContainerListener) list[i]).containerEvent(event);} A3StandardWrapper.getInitParameter()方法的实现如下 public String getInitParameter(String name) {return (findInitParameter(name));} A4findInitParameter()方法接收一个指定的初始化参数名的字符串变量调用HashMap 变量 parameters的get()方法获取初始化参数的值 public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter()synchronized (parameters) {return ((String) parameters.get(name)); // highlight line.}} method4getInitParameterNames()方法 该方法返回所有初始化参数的名字的集合实际上是 java.util.Enumeration的实例 public Enumeration getInitParameterNames() {synchronized (parameters) {return (new Enumerator(parameters.keySet()));}} 【3.4】servlet容器的父子关系1intro to StandardWrapperWrapper实例代表一个servlet实例是最低级的容器故Wrapper不能再有子容器不应该调用addChild()方法添加子容器否则抛出 java.lang.IllegalStateException 异常干货review——Wrapper实例代表一个servlet实例是最低级的容器故Wrapper不能再有子容器2org.apache.catalina.core.StandardWrapper.addChild()方法实现如下public void addChild(Container child) {throw new IllegalStateException (sm.getString(standardWrapper.notChild));}AttentionWrapper容器的父容器只能是 Context 容器若我们在设置父容器的时候传入了非Context容器则抛出 java.lang.IllegalArgumentException 异常public void setParent(Container container) { // org.apache.catalina.core.StandardWrapper.setParent().if ((container ! null) !(container instanceof Context)) throw new IllegalArgumentException(sm.getString(standardWrapper.notContext));if (container instanceof StandardContext) {swallowOutput ((StandardContext)container).getSwallowOutput();}super.setParent(container); // highlight line.}
public void setParent(Container container) { // org.apache.catalina.core.ContainerBase.setParent().Container oldParent this.parent;this.parent container;support.firePropertyChange(parent, oldParent, this.parent);}【4】 StandardWrapperFacade类 干货——应用了设计模式中的外观模式1problemsolution1.1problemStandardWrapper实例会调用它所载入的servlet类的实例的init()方法。init()方法需要一个javax.servlet.ServletConfig 实例而StandardWrapper了本身也实现了 javax.servlet.ServletConfig 接口所以理论上 StandardWrapper需要将其中大部分公共方法对servlet程序员隐藏起来 1.2solution为了实现这个目的StandardWrapper类将自身实例包装成 StandardWrapperFacade类的一个实例 2StandardWrapper类创建StandardWrapperFacade对象并将自身作为参数传入StandardWrapperFacade的构造器private StandardWrapperFacade facade new StandardWrapperFacade(this); // defined in StandardWrapper.java3StandardWrapperFacade的构造函数public StandardWrapperFacade(StandardWrapper config) {super();this.config (ServletConfig) config;
// private ServletConfig config null;}4因此当创建StandardWrapper对象调用servlet实例的 init()方法时它会传入StandardWrapperFacade类的一个实例。这样在servlet实例内调用 ServletConfig.getServletName(),ServletConfig.getInitParameter(), getInitParameterNames() getServletContext()方法会直接传递给 StandardWrapper类的相应方法5org.apache.catalina.core.StandardWrapperFacade 的定义如下public final class StandardWrapperFacade implements ServletConfig {public StandardWrapperFacade(StandardWrapper config) {super();this.config (ServletConfig) config;}public String getServletName() {return config.getServletName();}public ServletContext getServletContext() {ServletContext theContext config.getServletContext();if ((theContext ! null) (theContext instanceof ApplicationContext))theContext ((ApplicationContext) theContext).getFacade();return (theContext);}public String getInitParameter(String name) {return config.getInitParameter(name);}public Enumeration getInitParameterNames() {return config.getInitParameterNames();}
}【5】StandardWrapperValve类1StandardWrapperValve类是 StandardWrapper实例中的基础阀要完成两个操作Operationspublic StandardWrapper() { // StandardWrapper的构造函数super();swValvenew StandardWrapperValve();pipeline.setBasic(swValve);}O1执行与该servlet实例关联的全部过滤器干货——这里引入了过滤器 O2调用servlet实例的service()方法 2完成上述任务后在 StandardWrapperValve.invoke()方法实现中会执行以下操作OperationsO1调用StandardWrapper.allocate()方法获取该StandardWrapper实例所表示的 servlet实例 public void invoke(Request request, Response response,ValveContext valveContext)throws IOException, ServletException {long t1System.currentTimeMillis();requestCount;// Initialize local variables we may needboolean unavailable false;Throwable throwable null;StandardWrapper wrapper (StandardWrapper) 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;// Check for the application being marked unavailableif (!((Context) wrapper.getParent()).getAvailable()) {hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString(standardContext.isUnavailable));unavailable true;}// Check for the servlet being marked unavailableif (!unavailable wrapper.isUnavailable()) {log(sm.getString(standardWrapper.isUnavailable,wrapper.getName()));if (hres null) {; // NOTE - Not much we can do generically} else {long available wrapper.getAvailable();if ((available 0L) (available Long.MAX_VALUE))hres.setDateHeader(Retry-After, available);hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,sm.getString(standardWrapper.isUnavailable,wrapper.getName()));}unavailable true;}// Allocate a servlet instance to process this requesttry {if (!unavailable) {servlet wrapper.allocate(); // highlight line.}
// ...... O2调用私有方法 createFilterChain()创建过滤器链 // Create the filter chain for this request
ApplicationFilterChain filterChain createFilterChain(request, servlet); // for create FilterChain方法本章节末尾 private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) { if (servlet null)return (null);ApplicationFilterChain filterChain new ApplicationFilterChain(); filterChain.setServlet(servlet);StandardWrapper wrapper (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());// Acquire the filter mappings for this ContextStandardContext context (StandardContext) wrapper.getParent();FilterMap filterMaps[] context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps null) || (filterMaps.length 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath null;if (request instanceof HttpRequest) {HttpServletRequest hreq (HttpServletRequest) request.getRequest();String contextPath hreq.getContextPath();if (contextPath null)contextPath ;String requestURI ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() contextPath.length())requestPath requestURI.substring(contextPath.length());}String servletName wrapper.getName();int n 0;// Add the relevant path-mapped filters to this filter chainfor (int i 0; i filterMaps.length; i) {if (!matchFiltersURL(filterMaps[i], requestPath))continue;ApplicationFilterConfig filterConfig (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig null) {continue;}filterChain.addFilter(filterConfig);n;}// Add filters that match on servlet name secondfor (int i 0; i filterMaps.length; i) {if (!matchFiltersServlet(filterMaps[i], servletName))continue;ApplicationFilterConfig filterConfig (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig null) {continue;}filterChain.addFilter(filterConfig);n;}return (filterChain);} O3调用过滤器链的 doFilter()方法其中包括调用servlet实例的service()方法 try {String jspFile wrapper.getJspFile();if (jspFile ! null)sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);elsesreq.removeAttribute(Globals.JSP_FILE_ATTR);if ((servlet ! null) (filterChain ! null)) {filterChain.doFilter(sreq, sres); // hightlight line.}sreq.removeAttribute(Globals.JSP_FILE_ATTR);
// ......public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catlina.core.ApplicationFilterChain.doFileter()throws IOException, ServletException {if( System.getSecurityManager() ! null ) {final ServletRequest req request;final ServletResponse res response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res); // highlight line. internalDoFilter() 参见文末.return null;}});} catch( PrivilegedActionException pe) {Exception e pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}} private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException { //org.apache.catalina.core.ApplicationFilterChain.internalDoFilter().// Construct an iterator the first time this method is calledif (this.iterator null)this.iterator filters.iterator();// Call the next filter if there is oneif (this.iterator.hasNext()) {ApplicationFilterConfig filterConfig (ApplicationFilterConfig) iterator.next();Filter filter null;try {filter filterConfig.getFilter();support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,filter, request, response);filter.doFilter(request, response, this);support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,filter, request, response);} //......return;}// We fell off the end of the chain -- call the servlet instancetry {support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,servlet, request, response);if ((request instanceof HttpServletRequest) (response instanceof HttpServletResponse)) { servlet.service((HttpServletRequest) request, (HttpServletResponse) response); // 这不就是你梦寐以求的service()方法吗哈哈。} else {servlet.service(request, response); // and this highlight line.}support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,servlet, request, response);} //......} O4释放过滤器链 try {if (filterChain ! null)filterChain.release(); // highlight line.} catch (Throwable e) {log(sm.getString(standardWrapper.releaseFilters,wrapper.getName()), e);if (throwable null) {throwable e;exception(request, response, e);}}
void release() { //org.apache.catalina.core.ApplicationFilterChain.release()this.filters.clear();this.iterator iterator;this.servlet null;} O5调用Wrapper实例的 deallocate()方法 // Deallocate the allocated servlet instancetry {if (servlet ! null) {wrapper.deallocate(servlet); // highlight line.}} catch (Throwable e) {log(sm.getString(standardWrapper.deallocateException,wrapper.getName()), e);if (throwable null) {throwable e;exception(request, response, e);}}public void deallocate(Servlet servlet) throws ServletException { //org.apache.catalina.core.StandardWrapper.deallocate()// If not SingleThreadModel, no action is requiredif (!singleThreadModel) {countAllocated--;return;}synchronized (instancePool) {countAllocated--;instancePool.push(servlet);instancePool.notify();}} O6若该servlet类再也不会被使用到调用Wrapper实例的unload()方法 // If this servlet has been marked permanently unavailable,// unload it and release this instancetry {if ((servlet ! null) (wrapper.getAvailable() Long.MAX_VALUE)) {wrapper.unload(); // highlight line.}} // ......long t2System.currentTimeMillis();long timet2-t1;processingTimetime;if( time maxTime ) maxTimetime;} Attention以上调用过程中最重要的是对 createFilterChain()方法和过滤器链的 doFilter()方法的调用。createFilterChain()方法创建一个 ApplicationFilterChain实例并将所有需要应用到该Wrapper实例所代表的servlet实例的过滤器添加到其中private ApplicationFilterChain createFilterChain(Request request,Servlet servlet) {if (servlet null)return (null);ApplicationFilterChain filterChain new ApplicationFilterChain();filterChain.setServlet(servlet);StandardWrapper wrapper (StandardWrapper) getContainer();filterChain.setSupport(wrapper.getInstanceSupport());StandardContext context (StandardContext) wrapper.getParent();FilterMap filterMaps[] context.findFilterMaps();if ((filterMaps null) || (filterMaps.length 0))return (filterChain);// Acquire the information we will need to match filter mappingsString requestPath null;if (request instanceof HttpRequest) {HttpServletRequest hreq (HttpServletRequest) request.getRequest();String contextPath hreq.getContextPath();if (contextPath null)contextPath ;String requestURI ((HttpRequest) request).getDecodedRequestURI();if (requestURI.length() contextPath.length())requestPath requestURI.substring(contextPath.length());}String servletName wrapper.getName();int n 0; // Add the relevant path-mapped filters to this filter chainfor (int i 0; i filterMaps.length; i) {if (!matchFiltersURL(filterMaps[i], requestPath)) continue;ApplicationFilterConfig filterConfig (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig null) {continue; }filterChain.addFilter(filterConfig); n;}// Add filters that match on servlet name secondfor (int i 0; i filterMaps.length; i) {if (!matchFiltersServlet(filterMaps[i], servletName)) continue;ApplicationFilterConfig filterConfig (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig null) {continue;}filterChain.addFilter(filterConfig);n;}return (filterChain); }
public synchronized void unload() throws ServletException {if (!singleThreadModel (instance null))return;unloading true;if (countAllocated 0) {int nRetries 0;while (nRetries 10) {if (nRetries 0) {log(Waiting for countAllocated instance(s) to be deallocated);}try {Thread.sleep(50);} catch (InterruptedException e) {;}nRetries;}}ClassLoader oldCtxClassLoader Thread.currentThread().getContextClassLoader();ClassLoader classLoader instance.getClass().getClassLoader();PrintStream out System.out;if (swallowOutput) {SystemLogHandler.startCapture();}try {instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_DESTROY_EVENT, instance);Thread.currentThread().setContextClassLoader(classLoader);instance.destroy();instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance);} catch (Throwable t) {instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_DESTROY_EVENT, instance, t);instance null;instancePool null;nInstances 0;fireContainerEvent(unload, this);unloading false;throw new ServletException(sm.getString(standardWrapper.destroyException, getName()),t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);if (swallowOutput) {String log SystemLogHandler.stopCapture();if (log ! null log.length() 0) {if (getServletContext() ! null) {getServletContext().log(log);} else {out.println(log);}}}}instance null;if (singleThreadModel (instancePool ! null)) {try {Thread.currentThread().setContextClassLoader(classLoader);while (!instancePool.isEmpty()) {((Servlet) instancePool.pop()).destroy();}} catch (Throwable t) {instancePool null;nInstances 0;unloading false;fireContainerEvent(unload, this);throw new ServletException(sm.getString(standardWrapper.destroyException,getName()), t);} finally {Thread.currentThread().setContextClassLoader(oldCtxClassLoader);}instancePool null;nInstances 0;}singleThreadModel false;unloading false;fireContainerEvent(unload, this); }【6】 FilterDef类org.apache.catalina.deploy.FilterDef1introFilterDef 是一个过滤器的定义2FilterDef类中的每个属性表示在定义filter元素时声明的子元素。其中Map 类型的变量parameters 存储了初始化过滤器时所需要的所有参数。addInitParameter()方法用于向parameters 中添加新的 name/value 形式的参数名和对应的值3其定义源码如下public final class FilterDef { // org.apache.catalina.deploy.FilterDef private String description null;public String getDescription() {return (this.description);}public void setDescription(String description) {this.description description;} private String displayName null;public String getDisplayName() {return (this.displayName);}public void setDisplayName(String displayName) {this.displayName displayName;}private String filterClass null;public String getFilterClass() {return (this.filterClass);}public void setFilterClass(String filterClass) {this.filterClass filterClass;} private String filterName null;public String getFilterName() {return (this.filterName);}public void setFilterName(String filterName) {this.filterName filterName;} private String largeIcon null;public String getLargeIcon() {return (this.largeIcon);}public void setLargeIcon(String largeIcon) {this.largeIcon largeIcon;} private Map parameters new HashMap();public Map getParameterMap() {return (this.parameters);}private String smallIcon null;public String getSmallIcon() {return (this.smallIcon);}public void setSmallIcon(String smallIcon) {this.smallIcon smallIcon;} public void addInitParameter(String name, String value) {parameters.put(name, value);}public String toString() {StringBuffer sb new StringBuffer(FilterDef[);sb.append(filterName);sb.append(this.filterName);sb.append(, filterClass);sb.append(this.filterClass);sb.append(]);return (sb.toString());}
}【7】ApplicationFilterConfig类org.apache.catalina.core.ApplicationFilterConfig-应用过滤器配置类1introApplicationFilterConfig类实现了 javax.servlet.FilterConfig接口该类用于管理web 应用程序第1次启动时创建的所有过滤器实例2类签名final class ApplicationFilterConfig implements FilterConfig 3可以通过把一个 org.apache.catalina.Context对象和 一个 FilterDef对象传递给 ApplicationFilterConfig类的构造函数来创建一个 ApplicationFilterConfig对象 public ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException,IllegalAccessException, InstantiationException,ServletException {super();this.context context;setFilterDef(filterDef);}对以上代码的分析AnalysisA1Context对象表示一个web 应用程序 A2FilterDef对象表示一个过滤器的定义 4ApplicationFilterConfig.getFilter()方法会返回一个 javax.servlet.Filter对象该方法负责载入并实例化一个过滤器类 public String getFilterName() { // org.apache.catalina.core.ApplicationFilterConfig.getFilterName().return (filterDef.getFilterName());
}【8】ApplicationFilterChain类org.apache.catalina.core.ApplicationFilterChain1intro ApplicationFilterChain类实现了 javax.servlet.FilterChain接口StandardWrapperValve.invoke() 方法会创建 ApplicationFilterChain类的一个实例并调用其 doFilter()方法2Filter接口的doFilter()方法的签名如下public interface Filter { // javax.servlet.Filterpublic void init(FilterConfig filterConfig) throws ServletException; public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException; public void destroy();
}3ApplicationFilterChain.doFilter()方法会将 ApplicationFilterChain 类自身作为第3个参数传递给过滤器的 doFilter()方法public void doFilter(ServletRequest request, ServletResponse response) //org.apache.catalina.ApplicationFilterChain.doFileter().throws IOException, ServletException {if( System.getSecurityManager() ! null ) {final ServletRequest req request;final ServletResponse res response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction(){public Object run() throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch( PrivilegedActionException pe) {Exception e pe.getException();if (e instanceof ServletException)throw (ServletException) e;else if (e instanceof IOException)throw (IOException) e;else if (e instanceof RuntimeException)throw (RuntimeException) e;elsethrow new ServletException(e.getMessage(), e);}} else {internalDoFilter(request,response);}}4在Filter.doFilter()方法中 可以通过显示地调用 FileterChain.doFilter()方法来调用另一个过滤器。对以上代码的分析AnalysisA1正如你所看到的在doFilter()方法的最后一行会调用FilterChain.doFilter()方法 A2)如果某个过滤器时过滤器链中的最后一个过滤器则会调用被请求的 servlet类的 service()方法。如果过滤器没有调用chain.doFilter()方法则不会调用后面的过滤器 【9】应用程序0servlet文件目录 1程序源代码 public final class Bootstrap {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or http://localhost:8080/PrimitiveSystem.setProperty(catalina.base, System.getProperty(user.dir));Connector connector new HttpConnector();Wrapper wrapper1 new StandardWrapper();wrapper1.setName(Primitive);wrapper1.setServletClass(servlet.PrimitiveServlet); // attention for servlet class要与你的servlet目录相对应Wrapper wrapper2 new StandardWrapper();wrapper2.setName(Modern);wrapper2.setServletClass(servlet.ModernServlet); // attention for servlet class要与你的servlet目录相对应Context context new StandardContext();// StandardContexts start method adds a default mappercontext.setPath(/myApp);context.setDocBase(myApp);LifecycleListener listener new SimpleContextConfig();((Lifecycle) context).addLifecycleListener(listener);context.addChild(wrapper1);context.addChild(wrapper2);// for simplicity, we dont add a valve, but you can add// valves to context or wrapper just as you did in Chapter 6Loader loader new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping(/Primitive, Primitive);context.addServletMapping(/Modern, Modern);// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// wont startconnector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}}
} 2打印结果 E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\srcjava -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common.
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomca
t.chapter11.startup.Bootstrap
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException // // 这是从文件中加载 session对象到内存由于没有相关文件所以加载失败抛出异常但这不会影响我们访问servlet大家不要惊慌
java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.init(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.init(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55)
StandardManager[/myApp]: Exception loading sessions from persistent storage
java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.init(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.init(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter11.startup.Bootstrap.main(Bootstrap.java:55)
ModernServlet -- init