Tomcat架构中各个组件及组件间关系(二)

前言
由于换工作的原因,需要融入新的开发团队,开展新的业务征途,因此,距离上一次更新博客已有一段时间,现在稍微稳定下来可以继续Tomcat源码的分析。在回顾思路时发现,之前对于Tomcat组件和生命周期的文章主要从宏观角度分析了两者执行的大体流程,对于细节点的分析有所欠缺,而这些细节可能又是后期理解 “Tomcat处理请求响应” 这种重量级流程的前提。为了更好的温故而知新,未来将对Tomcat架构中各个组件及组件间关系Tomcat的生命周期两篇文章进行更深程度的剖析,本文是其中的第一部分,主要强化的内容如下:

  • Digester解析xml文件模式的详细分析,结合之前的文章,读者会了解到Tomcat中涉及到的所有关键解析规则和原理
  • 其他Connector、Container相关组件解析的详细过程。在之前的文章中,仅以<Server>顶层标签举例,并不涉及<Engine><Host><Context>等“子标签”,而Tomcat标签的解析越往“子标签” 越复杂,越接近Tomcat处理请求响应的核心,因此为了进一步的深入也需要将“子标签”的解析吃透

Tomcat架构中各个组件及组件间关系中,我们已对Digester类工作的大体思路进行了分析,并且以<Server>标签的解析进行了举例,<Service><Server>的子标签,对应的Digester解析规则如下

图1. <Service>标签的解析规则

addObjectCreate(String pattern, String className, String attributeName)底层使用的规则为ObjectCreateRule,方法的第一个参数是pattern,表明了解析到什么标签才会使用配置的规则对标签的内容进行解析,和正则表达式匹配的作用类似。比如上图中的pattern为Server/Service表示解析到<Server>下的<Service>标签时运用规则进行解析,这里用/表示一种父子关系。 第二个参数className很明显表示标签对应的java实体类,从上图中来说<Service>标签对应的实体实际上就是StandardService,其实该参数是一个可选参数,可以传null,用第三个参数attributeName在运行时指定该标签对应的类,以图中举例就是说<Service>标签可以存在一个属性,属性名为className,当第二个参数没有指定时,Digester会自动解析该属性,并通过反射生成该类的实例再压入Digester内部的栈顶。
addSetProperties(String pattern)底层使用的规则为SetPropertiesRule,方法唯一的参数也是pattern,同样表示遇到何种标签才进行解析,SetPropertiesRule规则用于解析标签对应的属性。以上图举例,<Service>标签如下所示
图2. server.xml中Service标签

其属性只有name一个,那我们猜想在StandardService中可能存在一个该属性对应的set方法,看下StandardService的代码发现确实如此
图3. StandardService中setName方法

这里有一个小坑需要说明一下,实际上标签对应的实体类并不一定存在标签属性对应的set方法,并且也不是存在对应属性的set方法就会调用,理解这个细节我们需要进入到SetPropertiesRule类的begin()方法中
图4. SetPropertiesRule类的begin方法

红框处存在三个判断,第一个digester.isFakeAtrribute(top, name),其中top是当前Digester内部栈中栈顶元素,对于<Service>而言栈顶元素就是StandardServicename是每一个属性的名称,isFakeAtrribute具体的逻辑如下
图5. isFakeAtrribute方法

该方法实际上就是在一个Map<Class,List<String>>的集合中判断某个类是否存在某个名称的属性,如果存在就返回true,进而不去调用该属性的set方法,那么有哪些属性被放在了这个“假属性”集合中呢?我们回头看Catalina中定义server.xml解析规则的方法
图6. Catalina的createStartDigester方法

Tomcat在创建Digester类之前默认添加了key为Object.class的entry,其value为包含className 的集合,结合图5代码的逻辑可知,如果标签没有设置特定的fake attributes,那么总会返回默认的,包含名称为className的集合。而正常情况下所有的标签都是没有设置特定的fake attributes的,也就是说Digester在解析所有标签时都会排除名称为className的属性
我们再来看图4判断中的第二个部分IntrospectionUtils.setProperty(top, name, value),最终调用的逻辑如 代码清单1

    public static boolean setProperty(Object o, String name, String value,
            boolean invokeSetProperty) {
        if (log.isDebugEnabled())
            log.debug("IntrospectionUtils: setProperty(" +
                    o.getClass() + " " + name + "=" + value + ")");

        String setter = "set" + capitalize(name);

        try {
            Method methods[] = findMethods(o.getClass());
            Method setPropertyMethodVoid = null;
            Method setPropertyMethodBool = null;

            // First, the ideal case - a setFoo( String ) method
            for (int i = 0; i < methods.length; i++) {
                Class<?> paramT[] = methods[i].getParameterTypes();
                if (setter.equals(methods[i].getName()) && paramT.length == 1
                        && "java.lang.String".equals(paramT[0].getName())) {

                    methods[i].invoke(o, new Object[] { value });
                    return true;
                }
            }

            // Try a setFoo ( int ) or ( boolean )
            for (int i = 0; i < methods.length; i++) {
                boolean ok = true;
                if (setter.equals(methods[i].getName())
                        && methods[i].getParameterTypes().length == 1) {

                    // match - find the type and invoke it
                    Class<?> paramType = methods[i].getParameterTypes()[0];
                    Object params[] = new Object[1];

                    // Try a setFoo ( int )
                    if ("java.lang.Integer".equals(paramType.getName())
                            || "int".equals(paramType.getName())) {
                        try {
                            params[0] = Integer.valueOf(value);
                        } catch (NumberFormatException ex) {
                            ok = false;
                        }
                    // Try a setFoo ( long )
                    }else if ("java.lang.Long".equals(paramType.getName())
                                || "long".equals(paramType.getName())) {
                            try {
                                params[0] = Long.valueOf(value);
                            } catch (NumberFormatException ex) {
                                ok = false;
                            }

                        // Try a setFoo ( boolean )
                    } else if ("java.lang.Boolean".equals(paramType.getName())
                            || "boolean".equals(paramType.getName())) {
                        params[0] = Boolean.valueOf(value);

                        // Try a setFoo ( InetAddress )
                    } else if ("java.net.InetAddress".equals(paramType
                            .getName())) {
                        try {
                            params[0] = InetAddress.getByName(value);
                        } catch (UnknownHostException exc) {
                            if (log.isDebugEnabled())
                                log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
                            ok = false;
                        }

                        // Unknown type
                    } else {
                        if (log.isDebugEnabled())
                            log.debug("IntrospectionUtils: Unknown type " +
                                    paramType.getName());
                    }

                    if (ok) {
                        methods[i].invoke(o, params);
                        return true;
                    }
                }

                // save "setProperty" for later
                if ("setProperty".equals(methods[i].getName())) {
                    if (methods[i].getReturnType()==Boolean.TYPE){
                        setPropertyMethodBool = methods[i];
                    }else {
                        setPropertyMethodVoid = methods[i];
                    }

                }
            }

            // Ok, no setXXX found, try a setProperty("name", "value")
            if (invokeSetProperty && (setPropertyMethodBool != null ||
                    setPropertyMethodVoid != null)) {
                Object params[] = new Object[2];
                params[0] = name;
                params[1] = value;
                if (setPropertyMethodBool != null) {
                    try {
                        return ((Boolean) setPropertyMethodBool.invoke(o,
                                params)).booleanValue();
                    }catch (IllegalArgumentException biae) {
                        //the boolean method had the wrong
                        //parameter types. lets try the other
                        if (setPropertyMethodVoid!=null) {
                            setPropertyMethodVoid.invoke(o, params);
                            return true;
                        }else {
                            throw biae;
                        }
                    }
                } else {
                    setPropertyMethodVoid.invoke(o, params);
                    return true;
                }
            }

        } catch (IllegalArgumentException ex2) {
            log.warn("IAE " + o + " " + name + " " + value, ex2);
        } catch (SecurityException ex1) {
            log.warn("IntrospectionUtils: SecurityException for " +
                    o.getClass() + " " + name + "=" + value + ")", ex1);
        } catch (IllegalAccessException iae) {
            log.warn("IntrospectionUtils: IllegalAccessException for " +
                    o.getClass() + " " + name + "=" + value + ")", iae);
        } catch (InvocationTargetException ie) {
            ExceptionUtils.handleThrowable(ie.getCause());
            log.warn("IntrospectionUtils: InvocationTargetException for " +
                    o.getClass() + " " + name + "=" + value + ")", ie);
        }
        return false;
    }

方法中将原始的方法名通过capitalize进行首字母大写处理,最终加上set前缀赋给setter变量。之后会根据类的实例得到对象所有的方法,并与setter进行匹配,匹配成功则直接invoke调用并返回true,没有找到对应属性的set方法则返回false。正是这种处理解释了上面我们提及的:有些标签即便配置了相关的属性也不会调用对应类的set方法
接下来我们再看第三个解析规则addSetNext(String pattern, String methodName, String paramType),底层使用的规则为SetNextRule,方法的第一个参数指明了触发该规则的具体模式,第二个参数表明调用父标签对应实体的方法名称,第三个参数就是方法参数的类型。在这里因为当前栈顶元素为StandardServiceaddSetNext会调用StandardServeraddService(Service service)方法,将当前StandardService与其父元素StandardServer建立关联,涉及相关代码如下

图7. StandardServer中addService方法

我们可以稍微总结一下Digester内置的三大解析规则类对应的用途

Rule 对应方法 用途
ObjectCreateRule addObjectCreate 根据匹配解析模式创建对应标签的实体类
SetPropertiesRule addSetProperties 根据匹配解析模式为对应标签实体类设置相关属性
SetNextRule addSetNext 建立标签对应实体之间子父类关系

至此<Service>标签的规则配置及解析流程分析完毕,我们接着看<Connector>标签

图8. Connector标签的解析规则

创建<Connector>对象的规则和<Service>规则不太一样,并没有使用Digester內建的ObjectCreateRule,而是自己继承Rule创建了ConnectorCreateRule。前文中分析过,当解析到对应标签的开始处会调用规则类的begin(),我们来看看它做了什么
图9. ObjectCreateRule的执行流程

方法中首先取出此时栈顶元素StandardService(Connector尚未创建),再从包含所有<Connector>标签属性的attributes中查找是否存在exector属性,存在最终会调用_setExecutor(Connector, Executor)方法,该方法的主要作用是设置处理端到端连接的线程池,默认情况下server.xml中并不会事先设置该线程池,但即便不设置,之后在Tomcat启动时也会默认创建一个,在后面分析启动流程时会详细分析,这里暂先按默认未设置线程池流程走。之后会根据protocol属性创建Connector对象,基于Tomcat架构中各个组件及组件间关系中给出的server.xml可知,<Connector>标签共有两种协议,一种是HTTP/1.1,另一种是AJP/1.3。前者大家很清楚是HTTP协议的1.1版本,后者一般用于web容器之间通信,比HTTP协议在web容器间拥有更高的吞吐量。因为存在两种协议,那就会存在两个Connector实体,为了突出重点,我们只分析最常用的HTTP协议对应的Connector初始化流程
图10. Connector构造器

Connector构造器会继续调用setProtocol(String protocol)方法,并将协议对应的字符串传入
图11. 确认协议对应处理器类型

第一个判断涉及到一种apr请求处理方法,可以将其理解为一种高效的IO模式,底层基于JNI技术调用操作系统级别的IO接口,在默认情况下也是关闭的。因此上述代码最终会调用setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol"),将Connector中的成员变量ProtocolHandler置为Http11Protocol,该类在处理请求响应的流程中起到了重要作用,后续文章会详细分析,这里记住即可
<Connector>在处理属性是也添加了名为SetAllPropertiesRule的规则,该规则接收了一个排除属性的数组,其中仅包含executor属性
图12. SetAllPropertiesRule

begin()方法中不仅按上面提到的流程对属性进行了筛选,而且根据该规则中设置的排除属性数组再一次进行了过滤。同样的,在addSetNext方法中会调用父标签StandardServiceaddConnector(Connector),从而建立父子关联关系
图13. StandardService中addConnector方法

在方法中使用了synchronized代码块解决了并发访问下新增Connector被覆盖的问题,在Tomcat的生命周期中说到,每一个容器都一个生命周期状态的概念,这里getState()就获得了此时Connector的状态,在刚创建时容器的state为NEWavailable属性值为false,并不会立即启动Connector容器,至此<Connector>的解析过程也分析完毕
Tomcat从整体架构上可以分为两大部分:监听请求并生成对应Request和Response的Connector连接器,以及处理请求和控制tomcat容器运转的Container<Engine>标签就是Container的顶层组件,每一个Engine相当于一个Servlet引擎,其下可以存在多个HostContext子容器
图14. Engine相关解析规则

乍一看貌似<Engine>相关的rule特别多,但仔细一看其实都是套路,按照上面的分析方式都能一一拿下,这里只说一些重点和不同的部分。规则中为<Engine>添加了一个名为EngineConfig的Listener,用于对StandardEngine组件的生命周期监控
图15. EngineConfig的所有逻辑

从图中可以看到该类在事件为start和stop时会进行日志的打印,此外并没有进行其他的操作,在StandardEngine初始化时存在一个管道Pipeline和阀门Valve的概念
图16. StandardEngine构造器

Tomcat中为了更高效的处理请求,内部设计了PipelineValve的概念,相当于Servlet中的Filter和FilterChain,管道中可以通过addValve(Valve)添加或通过removeValve(Valve)移除多个阀门,而有一种阀门被称为基础阀门,该阀门总是最后一个执行的,比如这里的StandardEngineValve,关于两者的详细分析会在后续文章开展,这里不做累述。参数backgroundProcessorDelayContainerBase中的内部类ContainerBackgroundProcessor有关,该类实现了Runnable接口,用于检测war包中的类文件是否改动,是否需要重新加载,而参数乘以默认的基数就是执行的间隔时间,具体的处理流程后续文章同样会讲到。setJvmRoute()是给该机器设置一个唯一的标识,当有多台机器组成cluster时,每台机器都会用这唯一的标识代表自身在集群中的位置
回到EngineRuleSet的规则定义上,我们发现Tomcat还为每一个StandardEngine添加了RealmRuleSet,该规则对应<Engine>的子标签<Realm>,此标签引出了一个“域”的概念,我们可以将多个web应用划分成多个域,给每个域设定不同的访问权限,只有拥有对应域访问权限的角色才能访问对应的web应用,因此该规则的设定主要为了安全访问和权限管理
一个<Host>表示一个虚拟主机,其下可以存在多个<Context>标签,<Host>标签对应的规则定义如下
图17. Host相关解析规则

<Host>对应的实体类为StandardHost,在初始化时也给Host容器中的管道添加了一个基础阀门StandardHostValve。同StandardEngine一样,Tomcat也为StandardHost添加了一个监听器HostConfig,但其功能远比EngineConfig复杂很多

图18. HostConfig中lifecycleEvent

它根据不同的事件类型对web应用的进行相应的检查发布,停止以及和上面提到的ContainerBackgroundProcessor线程结合起来监控应用是否需要reload等功能,这部分内容和容器的生命周期关系更加紧密,且可讲的内容较多,将放在生命周期强化的第二部分讲解
一个<Context>可以认为对应一个webapps下的目录,或者一个war包。代表虚拟主机的<Host>下可以存在多个<Context>标签,<Context>对应的解析规则也是继承RuleSetBase创建了自己的规则集合ContextRuleSet
图19. ContextRuleSet

标签对应的实体是StandardContext,也存在基础阀门StandardContextValve,添加了对应的监听器ContextConfig,在对该监听器进行说明之前不知道大家想过没有,到目前为止,我们一直在讨论server.xml文件的解析,那其他的xml文件,比如context.xmlweb.xml是什么时候解析的呢?为了回答这一问题,我们来看一下ContextConfig是如何处理监听事件发生的
图20. ContextConfig处理监听事件逻辑

Tomcat的生命周期中曾给出Tomcat生命周期流转图和全部生命周期状态字段,结合上图两处红框中的Lifecycle类型可知,Lifecycle.AFTER_INIT_EVENT发生在Lifecycle.CONFIGURE_START_EVENT之前,而前者的init()中就定义了解析web.xml文件的所有规则
图21. ContextConfig中init方法

继续深入
图22. createWebXmlDigester(boolean namespaceAware, boolean validation)

WebRuleSet同样继承了RuleSetweb.xml存在两种形式,一种是我们“通常”意义上,放在每一个war包内的webapps/WEB-INF/web.xml,该配置文件是以<web-app>作为根元素的;另一种是为了支持Servlet3.0新特性将web.xml分成多个小部分,运行时再将各个部分聚集起来解析的配置文件web-fragment.xml,该文件是以<web-fragment>作为根元素。代码清单2WebRuleSet设置的所有标签的解析规则

@Override
    public void addRuleInstances(Digester digester) {
        digester.addRule(fullPrefix,
                         new SetPublicIdRule("setPublicId"));
        digester.addRule(fullPrefix,
                         new IgnoreAnnotationsRule());
        digester.addRule(fullPrefix,
                new VersionRule());

        // Required for both fragments and non-fragments
        digester.addRule(fullPrefix + "/absolute-ordering", absoluteOrdering);
        digester.addRule(fullPrefix + "/ordering", relativeOrdering);

        if (fragment) {
            // web-fragment.xml
            digester.addRule(fullPrefix + "/name", name);
            digester.addCallMethod(fullPrefix + "/ordering/after/name",
                                   "addAfterOrdering", 0);
            digester.addCallMethod(fullPrefix + "/ordering/after/others",
                                   "addAfterOrderingOthers");
            digester.addCallMethod(fullPrefix + "/ordering/before/name",
                                   "addBeforeOrdering", 0);
            digester.addCallMethod(fullPrefix + "/ordering/before/others",
                                   "addBeforeOrderingOthers");
        } else {
            // web.xml
            digester.addCallMethod(fullPrefix + "/absolute-ordering/name",
                                   "addAbsoluteOrdering", 0);
            digester.addCallMethod(fullPrefix + "/absolute-ordering/others",
                                   "addAbsoluteOrderingOthers");
        }

        digester.addCallMethod(fullPrefix + "/context-param",
                               "addContextParam", 2);
        digester.addCallParam(fullPrefix + "/context-param/param-name", 0);
        digester.addCallParam(fullPrefix + "/context-param/param-value", 1);

        digester.addCallMethod(fullPrefix + "/display-name",
                               "setDisplayName", 0);

        digester.addRule(fullPrefix + "/distributable",
                         new SetDistributableRule());

        configureNamingRules(digester);

        digester.addObjectCreate(fullPrefix + "/error-page",
                                 "org.apache.catalina.deploy.ErrorPage");
        digester.addSetNext(fullPrefix + "/error-page",
                            "addErrorPage",
                            "org.apache.catalina.deploy.ErrorPage");

        digester.addCallMethod(fullPrefix + "/error-page/error-code",
                               "setErrorCode", 0);
        digester.addCallMethod(fullPrefix + "/error-page/exception-type",
                               "setExceptionType", 0);
        digester.addCallMethod(fullPrefix + "/error-page/location",
                               "setLocation", 0);

        digester.addObjectCreate(fullPrefix + "/filter",
                                 "org.apache.catalina.deploy.FilterDef");
        digester.addSetNext(fullPrefix + "/filter",
                            "addFilter",
                            "org.apache.catalina.deploy.FilterDef");

        digester.addCallMethod(fullPrefix + "/filter/description",
                               "setDescription", 0);
        digester.addCallMethod(fullPrefix + "/filter/display-name",
                               "setDisplayName", 0);
        digester.addCallMethod(fullPrefix + "/filter/filter-class",
                               "setFilterClass", 0);
        digester.addCallMethod(fullPrefix + "/filter/filter-name",
                               "setFilterName", 0);
        digester.addCallMethod(fullPrefix + "/filter/icon/large-icon",
                               "setLargeIcon", 0);
        digester.addCallMethod(fullPrefix + "/filter/icon/small-icon",
                               "setSmallIcon", 0);
        digester.addCallMethod(fullPrefix + "/filter/async-supported",
                "setAsyncSupported", 0);

        digester.addCallMethod(fullPrefix + "/filter/init-param",
                               "addInitParameter", 2);
        digester.addCallParam(fullPrefix + "/filter/init-param/param-name",
                              0);
        digester.addCallParam(fullPrefix + "/filter/init-param/param-value",
                              1);

        digester.addObjectCreate(fullPrefix + "/filter-mapping",
                                 "org.apache.catalina.deploy.FilterMap");
        digester.addSetNext(fullPrefix + "/filter-mapping",
                                 "addFilterMapping",
                                 "org.apache.catalina.deploy.FilterMap");

        digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name",
                               "setFilterName", 0);
        digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name",
                               "addServletName", 0);
        digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern",
                               "addURLPattern", 0);

        digester.addCallMethod(fullPrefix + "/filter-mapping/dispatcher",
                               "setDispatcher", 0);

         digester.addCallMethod(fullPrefix + "/listener/listener-class",
                                "addListener", 0);
         
        digester.addRule(fullPrefix + "/jsp-config",
                         jspConfig);

        digester.addObjectCreate(fullPrefix + "/jsp-config/jsp-property-group",
                                 "org.apache.catalina.deploy.JspPropertyGroup");
        digester.addSetNext(fullPrefix + "/jsp-config/jsp-property-group",
                            "addJspPropertyGroup",
                            "org.apache.catalina.deploy.JspPropertyGroup");
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/deferred-syntax-allowed-as-literal",
                               "setDeferredSyntax", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/el-ignored",
                               "setElIgnored", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-coda",
                               "addIncludeCoda", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-prelude",
                               "addIncludePrelude", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/is-xml",
                               "setIsXml", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/page-encoding",
                               "setPageEncoding", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/scripting-invalid",
                               "setScriptingInvalid", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/trim-directive-whitespaces",
                               "setTrimWhitespace", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/url-pattern",
                               "addUrlPattern", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/default-content-type",
                               "setDefaultContentType", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/buffer",
                               "setBuffer", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/error-on-undeclared-namespace",
                               "setErrorOnUndeclaredNamespace", 0);

        digester.addRule(fullPrefix + "/login-config",
                         loginConfig);

        digester.addObjectCreate(fullPrefix + "/login-config",
                                 "org.apache.catalina.deploy.LoginConfig");
        digester.addSetNext(fullPrefix + "/login-config",
                            "setLoginConfig",
                            "org.apache.catalina.deploy.LoginConfig");

        digester.addCallMethod(fullPrefix + "/login-config/auth-method",
                               "setAuthMethod", 0);
        digester.addCallMethod(fullPrefix + "/login-config/realm-name",
                               "setRealmName", 0);
        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-error-page",
                               "setErrorPage", 0);
        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-login-page",
                               "setLoginPage", 0);

        digester.addCallMethod(fullPrefix + "/mime-mapping",
                               "addMimeMapping", 2);
        digester.addCallParam(fullPrefix + "/mime-mapping/extension", 0);
        digester.addCallParam(fullPrefix + "/mime-mapping/mime-type", 1);


        digester.addObjectCreate(fullPrefix + "/security-constraint",
                                 "org.apache.catalina.deploy.SecurityConstraint");
        digester.addSetNext(fullPrefix + "/security-constraint",
                            "addSecurityConstraint",
                            "org.apache.catalina.deploy.SecurityConstraint");

        digester.addRule(fullPrefix + "/security-constraint/auth-constraint",
                         new SetAuthConstraintRule());
        digester.addCallMethod(fullPrefix + "/security-constraint/auth-constraint/role-name",
                               "addAuthRole", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/display-name",
                               "setDisplayName", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/user-data-constraint/transport-guarantee",
                               "setUserConstraint", 0);

        digester.addObjectCreate(fullPrefix + "/security-constraint/web-resource-collection",
                                 "org.apache.catalina.deploy.SecurityCollection");
        digester.addSetNext(fullPrefix + "/security-constraint/web-resource-collection",
                            "addCollection",
                            "org.apache.catalina.deploy.SecurityCollection");
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method",
                               "addMethod", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method-omission",
                               "addOmittedMethod", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/url-pattern",
                               "addPattern", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/web-resource-name",
                               "setName", 0);

        digester.addCallMethod(fullPrefix + "/security-role/role-name",
                               "addSecurityRole", 0);

        digester.addRule(fullPrefix + "/servlet",
                         new ServletDefCreateRule());
        digester.addSetNext(fullPrefix + "/servlet",
                            "addServlet",
                            "org.apache.catalina.deploy.ServletDef");

        digester.addCallMethod(fullPrefix + "/servlet/init-param",
                               "addInitParameter", 2);
        digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
                              0);
        digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
                              1);

        digester.addCallMethod(fullPrefix + "/servlet/jsp-file",
                               "setJspFile", 0);
        digester.addCallMethod(fullPrefix + "/servlet/load-on-startup",
                               "setLoadOnStartup", 0);
        digester.addCallMethod(fullPrefix + "/servlet/run-as/role-name",
                               "setRunAs", 0);

        digester.addObjectCreate(fullPrefix + "/servlet/security-role-ref",
                                 "org.apache.catalina.deploy.SecurityRoleRef");
        digester.addSetNext(fullPrefix + "/servlet/security-role-ref",
                            "addSecurityRoleRef",
                            "org.apache.catalina.deploy.SecurityRoleRef");
        digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-link",
                               "setLink", 0);
        digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-name",
                               "setName", 0);

        digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
                              "setServletClass", 0);
        digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
                              "setServletName", 0);
        
        digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
                                 "org.apache.catalina.deploy.MultipartDef");
        digester.addSetNext(fullPrefix + "/servlet/multipart-config",
                            "setMultipartDef",
                            "org.apache.catalina.deploy.MultipartDef");
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
                               "setLocation", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-file-size",
                               "setMaxFileSize", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-request-size",
                               "setMaxRequestSize", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/file-size-threshold",
                               "setFileSizeThreshold", 0);

        digester.addCallMethod(fullPrefix + "/servlet/async-supported",
                               "setAsyncSupported", 0);
        digester.addCallMethod(fullPrefix + "/servlet/enabled",
                               "setEnabled", 0);

        
        digester.addRule(fullPrefix + "/servlet-mapping",
                               new CallMethodMultiRule("addServletMapping", 2, 0));
        digester.addCallParam(fullPrefix + "/servlet-mapping/servlet-name", 1);
        digester.addRule(fullPrefix + "/servlet-mapping/url-pattern", new CallParamMultiRule(0));

        digester.addRule(fullPrefix + "/session-config", sessionConfig);
        digester.addObjectCreate(fullPrefix + "/session-config",
                                 "org.apache.catalina.deploy.SessionConfig");
        digester.addSetNext(fullPrefix + "/session-config", "setSessionConfig",
                            "org.apache.catalina.deploy.SessionConfig");
        digester.addCallMethod(fullPrefix + "/session-config/session-timeout",
                               "setSessionTimeout", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/name",
                               "setCookieName", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/domain",
                               "setCookieDomain", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/path",
                               "setCookiePath", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/comment",
                               "setCookieComment", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/http-only",
                               "setCookieHttpOnly", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/secure",
                               "setCookieSecure", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/max-age",
                               "setCookieMaxAge", 0);
        digester.addCallMethod(fullPrefix + "/session-config/tracking-mode",
                               "addSessionTrackingMode", 0);

        // Taglibs pre Servlet 2.4
        digester.addRule(fullPrefix + "/taglib", new TaglibLocationRule(false));
        digester.addCallMethod(fullPrefix + "/taglib",
                               "addTaglib", 2);
        digester.addCallParam(fullPrefix + "/taglib/taglib-location", 1);
        digester.addCallParam(fullPrefix + "/taglib/taglib-uri", 0);

        // Taglibs Servlet 2.4 onwards
        digester.addRule(fullPrefix + "/jsp-config/taglib", new TaglibLocationRule(true));
        digester.addCallMethod(fullPrefix + "/jsp-config/taglib",
                "addTaglib", 2);
        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-location", 1);
        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-uri", 0);

        digester.addCallMethod(fullPrefix + "/welcome-file-list/welcome-file",
                               "addWelcomeFile", 0);

        digester.addCallMethod(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping",
                              "addLocaleEncodingMapping", 2);
        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/locale", 0);
        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/encoding", 1);

        digester.addRule(fullPrefix + "/post-construct",
                new LifecycleCallbackRule("addPostConstructMethods", 2, true));
        digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-class", 0);
        digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-method", 1);

        digester.addRule(fullPrefix + "/pre-destroy",
                new LifecycleCallbackRule("addPreDestroyMethods", 2, false));
        digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-class", 0);
        digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-method", 1);
    }

其中的fullPrefix对应的就是上面两种xml文件的根元素。这里需要引入Digester中另外两个内置解析规则类CallMethodRuleCallParamRule,分别来源于digester.addCallMethod(String pattern, String methodName, int paramCount)digester.addCallParam(String pattern, int paramIndex),前者如果pattern匹配成功,会调用当前栈顶元素的名为methodName,属性数量为paramCount的方法;后者需要与前者配合使用,其含义为找到与pattern匹配的标签对应的值,该值作为前者调用方法的第paramIndex参数的值传入,举个例子来说

图23. 举例说明CallMethodRule和CallParamRule两种规则

相信写过web程序的读者都知道<context-param>的含义,当解析到第一句时会调用此时digester内部栈栈顶元素的addContextParam(String param, String value)方法,该方法有两个参数,当解析到子标签<param-name>时,将该标签的值对应方法中的第一个参数param,当解析到子标签<param-value>时,将该标签的值对应方法中的第二个参数value
从代码清单2中我们还可以知道<filter>对应的实体为FilterDef<filter-mapping>对应的实体为FilterMap<servlet>对应的实体又在ServletDefCreateRule中给出定义,为ServletDef。需要说明的一点是,这里只是定义了web.xml的解析规则,Tomcat将这些解析规则封装到了图22中的两个成员变量webRuleSetwebFragmentRuleSet中,真正的解析是在图20中,当ContextConfig监听到事件Lifecycle.CONFIGURE_START_EVENT,进而调用configureStart()时才发生的,具体的流程将在生命周期的补充文章中讲解

后记
未来计划用两到三篇文章对Tomcat容器生命周期相关知识点进行补充分析,主要包括两部分内容:

  1. 容器的初始化和启动流程
  2. 各个容器相关监听器在Tomcat运行时的作用
    完成这些前期准备工作后再用两到三篇文章对Tomcat如何接收处理请求,又如何正确的将请求分发到对应的war包,对应的Servlet中的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 前言借着上次对Tomcat类加载机制的分析,就想着看都看了,何不再看看Tomcat内部的实现原理和架构设计,向优秀...
    宝之家阅读 3,201评论 1 5
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • +今天是今日有所思第94天。 昨天清晨重读了《财富自由之路》24日的概念后,鬼使神差地选了个题目《如何选书》,不料...
    荒原苍狼阅读 216评论 0 0