tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析

【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

时间: 2024-10-07 22:21:56

tomcat(11)org.apache.catalina.core.StandardWrapper源码剖析的相关文章

tomcat(12)org.apache.catalina.core.StandardContext源码剖析

[0]README 0)本文部分文字描述转自 "how tomcat works",旨在学习 "tomcat(12)StandardContext源码剖析" 的基础知识: 1)Context实例表示一个具体的web 应用程序,其中包含一个或多个Wrapper实例,每个Wrapper 表示一个具体的servlet定义: 2)Context容器还需要其他组件的支持,如载入器和Session 管理器.本章要intro 的 StandardContext是 catalina

Apache Flink fault tolerance源码剖析(一)

因某些童鞋的建议,从这篇文章开始结合源码谈谈Flink Fault Tolerance相关的话题.上篇官方介绍的翻译是理解这个话题的前提,所以如果你想更深入得了解Flink Fault Tolerance的机制,推荐先读一下前篇文章理解它的实现原理.当然原理归原理,原理体现在代码实现里并不是想象中的那么直观.这里的源码剖析也是我学习以及理解的过程. 作为源码解析Flink Fault Tolerance的首篇文章,我们先暂且不谈太有深度的东西,先来了解一下:Flink哪里涉及到检查点/快照机制来

Apache Flink fault tolerance源码剖析(二)

继续Flink Fault Tolerance机制剖析.上一篇文章我们结合代码讲解了Flink中检查点是如何应用的(如何根据快照做失败恢复,以及检查点被应用的场景),这篇我们来谈谈检查点的触发机制以及基于Actor的消息驱动的协同机制.这篇涉及到一个非常关键的类--CheckpointCoordinator. org.apache.flink.runtime.checkpoint.CheckpointCoordinator 该类可以理解为检查点的协调器,用来协调operator和state的分布

Apache Flink fault tolerance源码剖析(四)

上篇文章我们探讨了Zookeeper在Flink的fault tolerance中发挥的作用(存储/恢复已完成的检查点以及检查点编号生成器). 这篇文章会谈论一种特殊的检查点,Flink将之命名为--Savepoint(保存点). 因为保存点只不过是一种特殊的检查点,所以在Flink中并没有太多代码实现.但作为一个特性,值得花费一个篇幅来介绍. 检查点VS保存点 使用数据流API编写的程序可以从保存点来恢复执行.保存点允许你在更新程序的同时还能保证Flink集群不丢失任何状态. 保存点是人工触发

Apache Flink fault tolerance源码剖析完结篇

这篇文章是对Flinkfault tolerance的一个总结.虽然还有些细节没有涉及到,但是基本的实现要点在这个系列中都已提及. 回顾这个系列,每篇文章都至少涉及一个知识点.我们来挨个总结一下. 恢复机制实现 Flink中通常需要进行状态恢复的对象是operator以及function.它们通过不同的方式来达到状态快照以及状态恢复的能力.其中function通过实现Checkpointed的接口,而operator通过实现StreamOpeator接口.这两个接口的行为是类似的. 当然对于数据

Apache Flink fault tolerance源码剖析(三)

上一篇文章我们探讨了基于定时任务的周期性检查点触发机制以及基于Akka的actor模型的消息驱动协同机制.这篇文章我们将探讨Zookeeper在Flink的Fault Tolerance所起到的作用. 其实,Flink引入Zookeeper的目的主要是让JobManager实现高可用(leader选举). 因为Zookeeper在Flink里存在多种应用场景,本篇我们还是将重心放在Fault Tolerance上,即讲解Zookeeper在检查点的恢复机制上发挥的作用. 如果用一幅图表示快照机制

Apache Flink fault tolerance源码剖析(五)

上一篇文章我们谈论了保存点的相关内容,其中就谈到了保存点状态的存储.这篇文章我们来探讨用户程序状态的存储,也是在之前的文章中多次提及的state backend(中文暂译为状态终端). 基于数据流API而编写的程序经常以各种各样的形式保存着状态: 窗口收集/聚合元素(这里的元素可以看作是窗口的状态)直到它们被触发 转换函数可能会使用key/value状态接口来存储数据 转换函数可能实现Checkpointed接口来让它们的本地变量受益于fault tolerant机制 当检查点机制工作时,上面谈

idea启动tomcat服务失败 SEVERE [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild:

我的工程是从eclipse生成的,个人习惯用idea开发.重复了一遍以往正常的不能再正常了的导入配置,结果遇到了如下问题: SEVERE [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start comp

struts2 启动tomcat时报错:org.apache.catalina.core.StandardContext filterStart

用struts2+jsp做个网站,到了网站制作后期时,启动tomcat竟然无法启动,挂了.之前一直有用可是今天添加了一些东西就挂了,删除之后还是无法启动tomcat服务器. 下面是报错的代码: <span style="color:#ff0000;"> org.apache.catalina.core.StandardContext filterStart SEVERE: Exception starting filter struts</span> <s