2017-11-17 22:46:35 +0000   |     java web how tomcat works container   |   Viewed times   |    

ContextConfig监听器为StandardContext实例配置

StandardContext实例的start()方法中,会触发一个BEFORE_START_EVENT事件,然后监听器org.apache.catalina.startup.ContextConfig实例(实现了LifecycleListener接口),开始对StandardContext实例进行配置。

// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);

配置过程中会用Digester库解析web.xml配置文件,文件的具体路径如下,

CATALINA_HOME/conf/web.xml

关于Digester库的细节,详见第15章。

默认Mapper

StandardContext定义的默认mapperClass字段是硬编码的,但也可以自己设置。

private String mapperClass = "org.apache.catalina.core.StandardContextMapper";

StandardContextMapper里最重要的是map()函数,匹配Wrapper容器的策略是“4步走”:

  1. Exaxt Match: 匹配relativeURI(relativeURI = requestURI - contextPath)
  2. Prifix Match: 匹配relativeURI + /*
  3. Extension Match: 匹配*.扩展名
  4. Default Match: 匹配/

比如我的路径127.0.0.1:8080/Primitive,首先contextPath为空,requestURI就是端口后面的部分/Primitive,然后从requestURI里面出去contextPath,剩下的就是一个相对路径relativeURI。因为contextPath为空,所以最后relativeURI就是/Primitive

contextPath = ""
requestURI = "/Primitive"
relativeURI = "/Primitive"

我们的例子里根据第一步”Exaxt Match”就匹配到了Servlet Name = Primitive。 因为在Bootstrap.java里设置了从URI到”Servlet Name”的映射,这样就能帮助我们找到需要加载的Wrapper

context.addServletMapping("/Primitive", "Primitive");

然后再查找StandardWrapper里设置的实际类全具名PrimitiveServlet就不是Mapper的工作了,

Wrapper wrapper1 = new StandardWrapper();
wrapper1.setName("Primitive");
wrapper1.setServletClass("PrimitiveServlet");
public Container map(Request request, boolean update) {


    int debug = context.getDebug();

    // Has this request already been mapped?
    if (update && (request.getWrapper() != null))
        return (request.getWrapper());

    // Identify the context-relative URI to be mapped
    String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath();
    String requestURI = ((HttpRequest) request).getDecodedRequestURI();
    String relativeURI = requestURI.substring(contextPath.length());

    if (debug >= 1)
        context.log("Mapping contextPath='" + contextPath +
                    "' with requestURI='" + requestURI +
                    "' and relativeURI='" + relativeURI + "'");

    // Apply the standard request URI mapping rules from the specification
    Wrapper wrapper = null;
    String servletPath = relativeURI;
    String pathInfo = null;
    String name = null;

    // Rule 1 -- Exact Match
    if (wrapper == null) {
        if (debug >= 2)
            context.log("  Trying exact match");
        if (!(relativeURI.equals("/")))
            name = context.findServletMapping(relativeURI);
        if (name != null)
            wrapper = (Wrapper) context.findChild(name);
        if (wrapper != null) {
            servletPath = relativeURI;
            pathInfo = null;
        }
    }

    // Rule 2 -- Prefix Match
    if (wrapper == null) {
        if (debug >= 2)
            context.log("  Trying prefix match");
        servletPath = relativeURI;
        while (true) {
            name = context.findServletMapping(servletPath + "/*");
            if (name != null)
                wrapper = (Wrapper) context.findChild(name);
            if (wrapper != null) {
                pathInfo = relativeURI.substring(servletPath.length());
                if (pathInfo.length() == 0)
                    pathInfo = null;
                break;
            }
            int slash = servletPath.lastIndexOf('/');
            if (slash < 0)
                break;
            servletPath = servletPath.substring(0, slash);
        }
    }

    // Rule 3 -- Extension Match
    if (wrapper == null) {
        if (debug >= 2)
            context.log("  Trying extension match");
        int slash = relativeURI.lastIndexOf('/');
        if (slash >= 0) {
            String last = relativeURI.substring(slash);
            int period = last.lastIndexOf('.');
            if (period >= 0) {
                String pattern = "*" + last.substring(period);
                name = context.findServletMapping(pattern);
                if (name != null)
                    wrapper = (Wrapper) context.findChild(name);
                if (wrapper != null) {
                    servletPath = relativeURI;
                    pathInfo = null;
                }
            }
        }
    }

    // Rule 4 -- Default Match
    if (wrapper == null) {
        if (debug >= 2)
            context.log("  Trying default match");
        name = context.findServletMapping("/");
        if (name != null)
            wrapper = (Wrapper) context.findChild(name);
        if (wrapper != null) {
            servletPath = relativeURI;
            pathInfo = null;
        }
    }

    // Update the Request (if requested) and return this Wrapper
    if ((debug >= 1) && (wrapper != null))
        context.log(" Mapped to servlet '" + wrapper.getName() +
                    "' with servlet path '" + servletPath +
                    "' and path info '" + pathInfo +
                    "' and update=" + update);
    if (update) {
        request.setWrapper(wrapper);
        ((HttpRequest) request).setServletPath(servletPath);
        ((HttpRequest) request).setPathInfo(pathInfo);
    }
    return (wrapper);

}

重载

web.xml文件发生变化,或者WEB-INF/classes目录下的其中一个文件被重新编译后,应用程序会重载。

Tomcat 4中WebappLoader用另一个线程周期性地检查WEB-INF目录中的所有类和JAR文件的时间戳。需要StandardContextWebappLoader是双向绑定的。这样加载器也能找到容器。

所以Tomcat 4运行Context容器的加载器和Session管理器这些组件都需要自己的后台线程。这就会导致资源浪费。

Tomcat 5用ContainerBackgroundProcessor类用一个后台线程统一检查WEB-INF目录中类的时间戳,还要帮助Session管理器检查会话有效期。一个后台线程大管家。

它通过ContainerBasestart()方法调用threadStart()方法启动。

它的processChildren()方法会调用自身容器的backgroundProgress()方法,然后递归调用每个子容器的processChildren()。这样可以确保每个子容器的backgroundProgress()方法都被调用。

重新理清路径

实际应用中: