【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)对于每个引入的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