【0】README
0.0)本文部分文字描述转自 “how tomcat works”,旨在学习 “tomcat(11)StandardWrapper源码剖析” 的基础知识;
0.1)StandardWrapper 是 Catalina中对Wrapper接口的标准实现;要知道,tomcat 中有4种类型的容器:Engine,Host,Context 和 Wrapper;(干货——review tomcat
中有4种类型的容器:Engine,Host,Context 和 Wrapper)
【1】方法调用序列
1)对于每个引入的http 请求,连接器都会调用与其关联的servlet容器的 invoke() 方法。然后,servlet容器会调用其所有子容器的invoke() 方法;
2)下图展示了连接器接收到http 请求后的方法调用的协作图;
3)上图的具体steps 如下:
step1)连接器创建 request 和 response对象;
step2)连接器调用StandardContext.invoke()方法;
step3)StandardContext.invoke()方法调用其管道的invoke() 方法。StandardContext的管道对象的基础阀是 StandardCoantextValve类的实例,因此, StandardContext 的管道会调用 StandardContextValve.invoke()方法;
step4)StandardContextValve.invoke()方法 获取相应的Wrapper 实例处理 http请求,调用Wrapper实例的invoke()方法;
step5)StandardWrapper类是Wrapper接口的标准实现,StandardWrapper.invoke()方法 会调用其管道对象的invoke()方法;
step6)StandardWrapper的管道对象中的基础阀是 StandardWrapperValve 类的实例,因此,会调用StandardWrapperValve.invoke()方法,StandardWrapperValve.invoke()方法会调用Wrapper实例的 allocate() 方法获取servlet实例;
step7)allocate()方法调用load() 方法载入相应的servlet类,若已经载入,则无需重复载入;
step8)load()方法调用servlet实例的init()方法;
step9)StandardWrapperValve调用servlet.service()方法;
// Call the filter chain for this request // NOTE: This also calls the servlet's service() method try { // org.apache.catalina.core.StandardWrapperValve.invoke() String jspFile = wrapper.getJspFile(); if (jspFile != null) sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile); else sreq.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); }
Attention)StandardContext类的构造函数会设置StandardContextValve类的一个实例作为其基础阀;
public StandardContext() { // org.apache.catalina.core.StardardContext super(); pipeline.setBasic(new StandardContextValve()); namingResources.setContainer(this); }
Attention)StandardWrapper类的构造函数也会设置一个 StandardWrapperValve实例作为其基础阀:
public StandardWrapper() { // org.apache.catalina.core.StardardWrapper super(); swValve=new StandardWrapperValve(); pipeline.setBasic(swValve); }
4)依据上述(3)小节中的 “处理http 请求的方法调用协作图”,本文按照惯例给出了具体的调用过程,如下:
4.1)本文第一张是借用了 “tomcat(10)安全性中章节【6.4】中Supplement-补充模块”的第2张图;(for
spec info,please 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)
对上述协作图和详细调用过程图的分析(Analysis):
A0)要知道Tomcat中有4种容器:Engine,Host,Context 和 Wrapper;(干货——本文一直强调这一点,理解容器的层次结构对于理解tomcat非常重要)
A1)StandardContext 和 StandardWrapper 都是容器:他们都继承自 ContainerBase,只不过StandardWrapper是StandardContext的子容器,而StandardWrapper是最小的容器,即它没有子容器;
A2)下面分别看StandardWrapper,StandardContext的构造函数 和 ContainerBase 的变量定义;
public final class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper { public StandardWrapper() { super(); swValve=new 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(已经被弃用了)
1)intro:servlet类可以实现 javax.servlet.SingleThreadModel 接口,这样的servlet类也称为 SingleThreadModel(STM)servlet类。根据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】StandardWrapper
1)intro to StandardWrapper:其主要任务是 载入它所代表的servlet类,并进行实例化;
2)StandardWrapper并不调用servlet的service方法,该任务由 StandardWrapperValve对象(StandardWrapper实例的管道对象中的基础阀)完成;
3)StandardWrapperValve对象通过调用allocate()方法从 StandardWrapper实例中获取servlet实例,在获得servlet实例后,StandardWrapperValve实例就会调用servlet实例的service()方法;
【3.1】分配servlet实例
1)分配servlet实例是由 StandardWrapper.allocate()方法来完成的(allocate方法返回请求的servelt实例);
2)allocate()方法分为两部分(parts):
p1)第一部分: 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 exception if (unloading) throw new ServletException (sm.getString("standardWrapper.unloading", getName())); // If not SingleThreadedModel, return the same instance every time if (!singleThreadModel) { // Load and initialize our instance if necessary if (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 wait if (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类
1)StandardWrapper类实现了Wrapper接口的 load() 方法,load() 方法调用loadServlet()方法载入某个servlet类,并调用其 init() 方法,此时要传入一个 javax.servlet.ServletConfig实例作为参数;
2)loadServlet() 方法是如何工作的
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 pool if (!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 effective String 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 specified if (actualClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.notClass", getName())); } // Acquire an instance of the class loader to be used Loader 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 servlet if (isContainerProvidedServlet(actualClass)) { classLoader = this.getClass().getClassLoader(); log(sm.getString ("standardWrapper.containerServlet", getName())); } // Load the specified servlet class from the appropriate class loader Class 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 itself try { servlet = (Servlet) classClass.newInstance(); } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.notServlet", actualClass), e); } catch (Throwable e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.instantiate", actualClass), e); } // Check if loading the servlet in this web application should be // allowed if (!isServletAllowed(servlet)) { throw new SecurityException (sm.getString("standardWrapper.privilegedServlet", actualClass)); } // Special handling for ContainerServlet instances if ((servlet instanceof ContainerServlet) && isContainerProvidedServlet(actualClass)) { ((ContainerServlet) servlet).setWrapper(this); } // Call the initialization method of this servlet try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet); servlet.init(facade); // Invoke jspInit on JSP pages if ((loadOnStartup >= 0) && (jspFile != null)) { // Invoking jspInit HttpRequestBase req = new HttpRequestBase(); HttpResponseBase res = new HttpResponseBase(); req.setServletPath(jspFile); req.setQueryString("jsp_precompile=true"); 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 instance singleThreadModel = 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 pool if (!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 style="font-family: SimSun; line-height: 1.5; background-color: inherit;"> </span>step5)如果找不到该jsp 页面的servlet类,则会使用变量 servletClass(actualClass)的值。若没有调用StandardWrapper.serServletClass() 方法设置servletClass的值,则会抛出异常,并停止执行后续方法;
// Complain if no servlet class has been specified if (actualClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.notClass", getName())); }<span style="font-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();step8)Catalina提供了一些用于访问servlet容器内部数据的专用servlet类。如果某个servlet类是这种专用的servlet,即若isContainerProvidedServlet()方法返回true,则变量 classLoader被赋值为另一种ClassLoader实例,如此一来,这个servlet实例就可以访问Catalina的内部数据了;
// Special case class loader for a container provided servlet if (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 loader Class 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 itself try { servlet = (Servlet) classClass.newInstance(); } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.notServlet", actualClass), e); } catch (Throwable e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.instantiate", actualClass), e); }
step11)在loadServlet()方法实例化这个servlet之前,它会调用 isServletAllowed()方法检查该servlet 类是否允许载入:
// Check if loading the servlet in this web application should be // allowed if (!isServletAllowed(servlet)) { throw new SecurityException (sm.getString("standardWrapper.privilegedServlet", actualClass)); }
step12)若通过了安全检查,它还会继续检查该servlet类是否是一个 ContainerServlet类型的servlet(实现了 org.apache.catalina.ContainerServlet接口的 servlet可以访问Catalina的内部功能)。若该servlet类是一个 ContainerServlet,loadServlet()方法会调用
ContainerServlet.setWrapper(),传入StandardWrapper实例;// Special handling for ContainerServlet instances if ((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 servlet try { 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 jspInit HttpRequestBase req = new HttpRequestBase(); HttpResponseBase res = new HttpResponseBase(); req.setServletPath(jspFile); req.setQueryString("jsp_precompile=true"); 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 instance singleThreadModel = 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); else return (((Context) parent).getServletContext()); }step18)最后返回已载入的servlet实例;
return servlet;
【3.3】ServletConfig对象
1)intro:在上述step13)中提到了 servlet.init(facade),而facade 是 javax.servlet.ServletConfig对象的一个外观变量;
2)StandardWrapper对象是如何获取 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(); }
3)javax.servlet.ServletConfig 接口有4个方法:getServletContext() , getServletName(), getInitParameter(), getInitParameterNames()方法;下面对这4个方法进行说明;
method1)getServletConfig()方法:
public ServletContext getServletContext() { // org.apache.catalina.core.StandardWrapper.getServletContext() if (parent == null) return (null); else if (!(parent instanceof Context)) return (null); else return (((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类的一个实例;
method2)getServletName()方法:该方法返回 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 ContainerBase return (name); }method3)getInitParameter()方法:该方法返回指定初始参数的值
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); }
A3)StandardWrapper.getInitParameter()方法的实现如下:public String getInitParameter(String name) { return (findInitParameter(name)); }A4)findInitParameter()方法接收一个指定的初始化参数名的字符串变量,调用HashMap 变量 parameters的get()方法获取初始化参数的值;
public String findInitParameter(String name) { // org.apache.catalina.core.StandardWrapper.findInitParameter() synchronized (parameters) { return ((String) parameters.get(name)); // highlight line. } }
method4)getInitParameterNames()方法: 该方法返回所有初始化参数的名字的集合,实际上是 java.util.Enumeration的实例;
public Enumeration getInitParameterNames() { synchronized (parameters) { return (new Enumerator(parameters.keySet())); } }
【3.4】servlet容器的父子关系
1)intro to StandardWrapper:Wrapper实例代表一个servlet实例,是最低级的容器,故Wrapper不能再有子容器,不应该调用addChild()方法添加子容器,否则抛出 java.lang.IllegalStateException 异常;(干货review——Wrapper实例代表一个servlet实例,是最低级的容器,故Wrapper不能再有子容器)
2)org.apache.catalina.core.StandardWrapper.addChild()方法实现如下:
public void addChild(Container child) { throw new IllegalStateException (sm.getString("standardWrapper.notChild")); }
Attention)Wrapper容器的父容器只能是 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类 (干货——应用了设计模式中的外观模式)
1)problem+solution:
1.1)problem:StandardWrapper实例会调用它所载入的servlet类的实例的init()方法。init()方法需要一个javax.servlet.ServletConfig 实例,而StandardWrapper了本身也实现了 javax.servlet.ServletConfig 接口,所以,理论上
StandardWrapper需要将其中大部分公共方法对servlet程序员隐藏起来;1.2)solution:为了实现这个目的,StandardWrapper类将自身实例包装成 StandardWrapperFacade类的一个实例;
2)StandardWrapper类创建StandardWrapperFacade对象,并将自身作为参数传入StandardWrapperFacade的构造器;
private StandardWrapperFacade facade = new StandardWrapperFacade(this); // defined in StandardWrapper.java
3)StandardWrapperFacade的构造函数;
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类的相应方法;
5)org.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类
1)StandardWrapperValve类是 StandardWrapper实例中的基础阀,要完成两个操作(Operations):
public StandardWrapper() { // StandardWrapper的构造函数; super(); swValve=new StandardWrapperValve(); pipeline.setBasic(swValve); }
O1)执行与该servlet实例关联的全部过滤器;(干货——这里引入了过滤器)
O2)调用servlet实例的service()方法;
2)完成上述任务后,在 StandardWrapperValve.invoke()方法实现中会执行以下操作(Operations):
O1)调用StandardWrapper.allocate()方法获取该StandardWrapper实例所表示的 servlet实例;
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { long t1=System.currentTimeMillis(); requestCount++; // Initialize local variables we may need boolean 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 unavailable if (!((Context) wrapper.getParent()).getAvailable()) { hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); unavailable = true; } // Check for the servlet being marked unavailable if (!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 request try { 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 Context StandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done if ((filterMaps == null) || (filterMaps.length == 0)) return (filterChain); // Acquire the information we will need to match filter mappings String 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 chain for (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 second for (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); else sreq.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; else throw 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 called if (this.iterator == null) this.iterator = filters.iterator(); // Call the next filter if there is one if (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 instance try { 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 instance try { 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 required if (!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 instance try { if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { wrapper.unload(); // highlight line. } } // ...... long t2=System.currentTimeMillis(); long time=t2-t1; processingTime+=time; if( time > maxTime ) maxTime=time; }
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 mappings String 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 chain for (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 second for (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.FilterDef)
1)intro:FilterDef 是一个过滤器的定义;
2)FilterDef类中的每个属性表示在定义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-应用过滤器配置类)
1)intro:ApplicationFilterConfig类实现了 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); }
对以上代码的分析(Analysis):
A1)Context对象表示一个web 应用程序;
A2)FilterDef对象表示一个过滤器的定义;
4)ApplicationFilterConfig.getFilter()方法:会返回一个 javax.servlet.Filter对象,该方法负责载入并实例化一个过滤器类;
public String getFilterName() { // org.apache.catalina.core.ApplicationFilterConfig.getFilterName(). return (filterDef.getFilterName()); }
【8】ApplicationFilterChain类(org.apache.catalina.core.ApplicationFilterChain)
1)intro: ApplicationFilterChain类实现了 javax.servlet.FilterChain接口,StandardWrapperValve.invoke() 方法会创建 ApplicationFilterChain类的一个实例,并调用其 doFilter()方法;
2)Filter接口的doFilter()方法的签名如下:
public interface Filter { // javax.servlet.Filter public void init(FilterConfig filterConfig) throws ServletException; public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException; public void destroy(); }
3)ApplicationFilterChain.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; else throw new ServletException(e.getMessage(), e); } } else { internalDoFilter(request,response); } }
4)在Filter.doFilter()方法中, 可以通过显示地调用 FileterChain.doFilter()方法来调用另一个过滤器。
对以上代码的分析(Analysis):
A1)正如你所看到的,在doFilter()方法的最后一行会调用FilterChain.doFilter()方法;
A2)如果某个过滤器时过滤器链中的最后一个过滤器,则会调用被请求的 servlet类的 service()方法。如果过滤器没有调用chain.doFilter()方法,则不会调用后面的过滤器;
【9】应用程序
0)servlet文件目录
1)程序源代码
public final class Bootstrap { public static void main(String[] args) { //invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive System.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(); // StandardContext's start method adds a default mapper context.setPath("/myApp"); context.setDocBase("myApp"); LifecycleListener listener = new SimpleContextConfig(); ((Lifecycle) context).addLifecycleListener(listener); context.addChild(wrapper1); context.addChild(wrapper2); // for simplicity, we don't add a valve, but you can add // valves to context or wrapper just as you did in Chapter 6 Loader 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 // won't start connector.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\src>java -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.EOFException at 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.EOFException at 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