tomcat源码解析(三)——Digester类源码解析及Rule分析

来源:互联网 发布:域名和服务器怎么绑定 编辑:程序博客网 时间:2024/05/18 03:09

在这篇文章中主要针对tomcat源码中Rule部分的解析;这部分功能主要涉及server.xml文件加载和tomcat容器中各组件初始化的过程。在我之前的文章中《tomcat源码解析(一)——Bootstrap和Catalina启动部分》和《tomcat源码解析(二)——xml解析过程分析》分别对启动过程和xml解析进行了分析。

之前的文章中遗留了几个问题,将在这篇文章中进行解答;问题如下:

1、第一篇文章启动过程中Catalina类createStartDigester中设置过程及原因。

2、第二篇文章中关于rule,调用的

rule.begin(namespaceURI, name, list);

rule.body(namespaceURI, name, bodyText);

rule.end(namespaceURI, name);

rule.finish();

的具体过程和分析方法和Digester类的stack作用。


如果对tomcat使用SAX解析xml已经了解的可以较好理解本文的思路。


对于Rule的分析首先需要查看org.apache.tomcat.util.digester.Rule的源码,内容比较简单,就不放源码内容了;从中可以看到一些比较重要的信息:

1、Rule.java是一个是抽象类,对于单继承的java来说,其子类的功能职责一目了然。

2、Rule.java类所有的方法都不是抽象方法,而且具体实现都是空的(代表其子类可以自由的选择覆盖父类的方法,也可以不覆盖)

3、Rule.java类的子类比较多,这里只介绍部分,其他的类似。


现在可以正式分析Catalina的启动部分源码createStartDigester():

关于createStartDigester的源码如下:

    /**     * Create and configure the Digester we will be using for startup.     */    protected Digester createStartDigester() {        long t1=System.currentTimeMillis();        // Initialize the digester        Digester digester = new Digester();        digester.setValidating(false);        digester.setRulesValidation(true);        HashMap<Class, List<String>> fakeAttributes = new HashMap<Class, List<String>>();        ArrayList<String> attrs = new ArrayList<String>();        attrs.add("className");        fakeAttributes.put(Object.class, attrs);        digester.setFakeAttributes(fakeAttributes);        digester.setClassLoader(StandardServer.class.getClassLoader());        // Configure the actions we will be using        digester.addObjectCreate("Server",                                 "org.apache.catalina.core.StandardServer",                                 "className");        digester.addSetProperties("Server");        digester.addSetNext("Server",                            "setServer",                            "org.apache.catalina.Server");        digester.addObjectCreate("Server/GlobalNamingResources",                                 "org.apache.catalina.deploy.NamingResources");        digester.addSetProperties("Server/GlobalNamingResources");        digester.addSetNext("Server/GlobalNamingResources",                            "setGlobalNamingResources",                            "org.apache.catalina.deploy.NamingResources");        digester.addObjectCreate("Server/Listener",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties("Server/Listener");        digester.addSetNext("Server/Listener",                            "addLifecycleListener",                            "org.apache.catalina.LifecycleListener");        digester.addObjectCreate("Server/Service",                                 "org.apache.catalina.core.StandardService",                                 "className");        digester.addSetProperties("Server/Service");        digester.addSetNext("Server/Service",                            "addService",                            "org.apache.catalina.Service");        digester.addObjectCreate("Server/Service/Listener",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties("Server/Service/Listener");        digester.addSetNext("Server/Service/Listener",                            "addLifecycleListener",                            "org.apache.catalina.LifecycleListener");        //Executor        digester.addObjectCreate("Server/Service/Executor",                         "org.apache.catalina.core.StandardThreadExecutor",                         "className");        digester.addSetProperties("Server/Service/Executor");        digester.addSetNext("Server/Service/Executor",                            "addExecutor",                            "org.apache.catalina.Executor");                digester.addRule("Server/Service/Connector",                         new ConnectorCreateRule());        digester.addRule("Server/Service/Connector",                          new SetAllPropertiesRule(new String[]{"executor"}));        digester.addSetNext("Server/Service/Connector",                            "addConnector",                            "org.apache.catalina.connector.Connector");                        digester.addObjectCreate("Server/Service/Connector/Listener",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties("Server/Service/Connector/Listener");        digester.addSetNext("Server/Service/Connector/Listener",                            "addLifecycleListener",                            "org.apache.catalina.LifecycleListener");        // Add RuleSets for nested elements        digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));        digester.addRuleSet(new EngineRuleSet("Server/Service/"));        digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));        digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));        digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));        // When the 'engine' is found, set the parentClassLoader.        digester.addRule("Server/Service/Engine",                         new SetParentClassLoaderRule(parentClassLoader));        digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Cluster/"));        long t2=System.currentTimeMillis();        if (log.isDebugEnabled())            log.debug("Digester for server.xml created " + ( t2-t1 ));        return (digester);    }
以上代码的功能分析如下:

1、digester.setValidating(false);设置SAX解析xml的DTD校验和schema校验,调用源码可参考Digester类的getFactory()和Digester类的getParser();

2、digester.setRulesValidation(true);看源码中只和日志打印的功能相关,和逻辑功能无关,在后文的源码中可以看到。

3、digester.setFakeAttributes(fakeAttributes);设置需要过滤的属性,如果设置后在进行类初始化时,配置的属性不会赋值给实例化后的对象,具体功能和实现在下文中会有详细说明。

4、digester.setClassLoader(StandardServer.class.getClassLoader());由于启动时首先启动的是StandardServer,设置StandardServer所在的的classloader。

5、之后的操作可以发现实际上调用的方法分为如下几个:

digester.addObjectCreate(String pattern, String className, String attributeName)

digester.addSetProperties(String pattern)

digester.addSetNext(String pattern, String methodName, String paramType)

digester.addRule(String pattern, Rule rule)

digester.addRuleSet(RuleSet ruleSet)

到这里createStartDigester()的主体功能就算分析完了;


关于上面第5点这些方法,我们继续查看Digester类的实现源码,可以总结如下规律:

1、digester.addObjectCreate(String pattern, String className, String attributeName)实际上调用的是addRule(String pattern, Rule rule)方法;且第二个参数新创建了一个对象ObjectCreateRule(className, attributeName);

2、同理可以发现digester.addSetProperties(String pattern)调用的也是addRule(String pattern, Rule rule)方法,创建了SetPropertiesRule()对象

3、digester.addSetNext(String pattern, String methodName, String paramType)调用的也是addRule(String pattern, Rule rule)方法,创建了SetNextRule(methodName, paramType)对象

4、只有digester.addRuleSet(RuleSet ruleSet)不同,这个需要专门分析。


所以可以理解为createStartDigester()中第5部分只执行了

digester.addRule(String pattern, Rule rule)

digester.addRuleSet(RuleSet ruleSet)

两种操作。

所关注的Rule也集中在:ObjectCreateRule、SetPropertiesRule、SetNextRule这三个类上。


细跟digester.addRule(String pattern, Rule rule)源码,如下:

    /**     * <p>Register a new Rule matching the specified pattern.     * This method sets the <code>Digester</code> property on the rule.</p>     *     * @param pattern Element matching pattern     * @param rule Rule to be registered     */    public void addRule(String pattern, Rule rule) {        rule.setDigester(this);        getRules().add(pattern, rule);    }
相应的 getRules()源码:

    /**     * Return the <code>Rules</code> implementation object containing our     * rules collection and associated matching policy.  If none has been     * established, a default implementation will be created and returned.     */    public Rules getRules() {        if (this.rules == null) {            this.rules = new RulesBase();            this.rules.setDigester(this);        }        return (this.rules);    }
可以发现pattern和rule的映射关系是通过RulesBase()进行管理的;

继续查看RulesBase的add(String pattern, Rule rule)源码:

    /**     * Register a new Rule instance matching the specified pattern.     *     * @param pattern Nesting pattern to be matched for this Rule     * @param rule Rule instance to be registered     */    public void add(String pattern, Rule rule) {        // to help users who accidently add '/' to the end of their patterns        int patternLength = pattern.length();        if (patternLength>1 && pattern.endsWith("/")) {            pattern = pattern.substring(0, patternLength-1);        }                        List list = (List) cache.get(pattern);        if (list == null) {            list = new ArrayList();            cache.put(pattern, list);        }        list.add(rule);        rules.add(rule);        if (this.digester != null) {            rule.setDigester(this.digester);        }        if (this.namespaceURI != null) {            rule.setNamespaceURI(this.namespaceURI);        }    }
从中可以看到pattern和rule的映射关系是一对多的,且通过map方式进行管理的(cache实际上是map类型的),例如pattern为"Server"时,会对应三个Rule;而且RulesBase中会单独维护rules(额外的List对象)去记录所有添加的rule。


到这里,可以发现pattern的存在至关重要,全部都是Server开头,例如“Server/GlobalNamingResources”,“Server/Service/Connector”等,这些都会对应到server.xml的各个元素节点的层级关系。


现在可以结合上一篇文章的内容,分析出tomcat利用SAX解析server.xml时,在触发例如startElement的回调函数时,通过传入localName和qname参数生成match(例如:参数“Server”)取出对应的所有rules,并分别执行begin方法。

具体以下以解析pattern为"Server"的执行过程为例;

首先在Catalina类的createStartDigester()中执行了

        // Configure the actions we will be using        digester.addObjectCreate("Server",                                 "org.apache.catalina.core.StandardServer",                                 "className");        digester.addSetProperties("Server");        digester.addSetNext("Server",                            "setServer",                            "org.apache.catalina.Server");
实际是在pattern为"Server"中设置了3个Rule,他们分别是ObjectCreateRule(className, attributeName),SetPropertiesRule(),SetNextRule(methodName, paramType);

当Catalina的load()函数执行到digester.parse(inputSource);时,通过SAX解析server.xml后,遇到开始节点<Server>,并在开始解析时触发Digester类的startElement方法。

在startElement方法中生成match=“Server”;即xml中的元素节点就是pattern,它们之间的对应关系生成规则可参考Digester类的startElement源码:

        // Compute the current matching rule        StringBuffer sb = new StringBuffer(match);        if (match.length() > 0) {            sb.append('/');        }        sb.append(name);        match = sb.toString();
match是全局变量,每次修改都是基于上一次的match;现在得到match及传入的参数namespaceURI就可以从RulesBase类中找到对应的Rules

// Fire "begin" events for all relevant rules        List rules = getRules().match(namespaceURI, match);
里面具体获取rules的源码读者可以自己跟进阅读。

获取到rules先放到matches(栈结构)中,再分别执行rule.begin(namespaceURI, name, list);方法(在下文中会继续说明)。

继续执行到xml中</Server>时会触发endElement方法,先通过matches.pops得到对应的rules(由于是栈结构,到这里得到的自然是match=“Server”时设置的rules)。

然后分别将这些rules执行rule.body(namespaceURI, name, bodyText);和 rule.end(namespaceURI, name);方法;

match在endElement方法中,会返回到上一级pattern。

        // Recover the previous match expression        int slash = match.lastIndexOf('/');        if (slash >= 0) {            match = match.substring(0, slash);        } else {            match = "";        }

最后执行endDocument时,获取到加入过的所有pattern对应的rules,并执行rule.finish();

        // Fire "finish" events for all defined rules        Iterator rules = getRules().rules().iterator();        while (rules.hasNext()) {            Rule rule = (Rule) rules.next();            try {                rule.finish();            } catch (Exception e) {                log.error("Finish event threw exception", e);                throw createSAXException(e);            } catch (Error e) {                log.error("Finish event threw error", e);                throw e;            }        }

到这里我们梳理了一个比较完整的xml解析流程。现在关注点代码集中在

rule.begin(namespaceURI, name, list);

rule.body(namespaceURI, name, bodyText);

rule.end(namespaceURI, name);

rule.finish();

这四个部分;

而关注的主要rule也集中在如下几个类上:

ObjectCreateRule

SetPropertiesRule

SetNextRule

除此之外还有很多Rule,遇到时我再具体分析。以上三个类比较有特点;


ObjectCreateRule的源码分析:

当代码执行rule.begin(namespaceURI, name, list);时,由于ObjectCreateRule没有重写begin(String namespace, String name, Attributes attributes)方法,所以调用Rule的begin(String namespace, String name, Attributes attributes)方法,如下所示:

    /**     * This method is called when the beginning of a matching XML element     * is encountered. The default implementation delegates to the deprecated     * method {@link #begin(Attributes) begin} without the      * <code>namespace</code> and <code>name</code> parameters, to retain      * backwards compatibility.     *     * @param namespace the namespace URI of the matching element, or an      *   empty string if the parser is not namespace aware or the element has     *   no namespace     * @param name the local name if the parser is namespace aware, or just      *   the element name otherwise     * @param attributes The attribute list of this element     * @since Digester 1.4     */    public void begin(String namespace, String name, Attributes attributes)        throws Exception {        begin(attributes);    }

其中ObjectCreateRule重写了begin(Attributes attributes);源码如下:

    /**     * Process the beginning of this element.     *     * @param attributes The attribute list of this element     */    public void begin(Attributes attributes) throws Exception {        // Identify the name of the class to instantiate        String realClassName = className;        if (attributeName != null) {            String value = attributes.getValue(attributeName);            if (value != null) {                realClassName = value;            }        }        if (digester.log.isDebugEnabled()) {            digester.log.debug("[ObjectCreateRule]{" + digester.match +                    "}New " + realClassName);        }        // Instantiate the new object and push it on the context stack        Class clazz = digester.getClassLoader().loadClass(realClassName);        Object instance = clazz.newInstance();        digester.push(instance);    }
里面的功能是将addObjectCreate在构造函数中传入的className进行实例化,例如将pattern为"Server"的org.apache.catalina.core.StandardServer实例化,

并放入Digester类的栈stack对象中(Digester类的root对象一般是Catalina类的实例化对象自身,这个stack后面会专门讲解);

除此之外ObjectCreateRule重写了end()方法,但没有具体功能性代码,只是记录日志和执行stack.pop()。ObjectCreateRule并没有重写其它的Rule.java类的方法,对它的分析到这就算结束了。


SetPropertiesRule的源码分析:

在Digester类中调用源码如下

    /**     * Add a "set properties" rule for the specified parameters.     *     * @param pattern Element matching pattern     * @see SetPropertiesRule     */    public void addSetProperties(String pattern) {        addRule(pattern,                new SetPropertiesRule());    }
对应的SetPropertiesRule构造函数

    /**     * Base constructor.     */    public SetPropertiesRule() {        // nothing to set up     }
查看SetPropertiesRule源码部分可知,它重写的方法是begin(Attributes attributes);新加了addAlias(String attributeName, String propertyName)方法;

addAlias(String attributeName, String propertyName)方法是为属性添加别名,具体可以查看源码,其不影响主要流程的功能分析,不做讲解;

SetPropertiesRule的begin(Attributes attributes)的源码如下:

/**     * Process the beginning of this element.     *     * @param attributes The attribute list of this element     */    public void begin(Attributes attributes) throws Exception {                // Populate the corresponding properties of the top object        Object top = digester.peek();        if (digester.log.isDebugEnabled()) {            if (top != null) {                digester.log.debug("[SetPropertiesRule]{" + digester.match +                                   "} Set " + top.getClass().getName() +                                   " properties");            } else {                digester.log.debug("[SetPropertiesRule]{" + digester.match +                                   "} Set NULL properties");            }        }                // set up variables for custom names mappings        int attNamesLength = 0;        if (attributeNames != null) {            attNamesLength = attributeNames.length;        }        int propNamesLength = 0;        if (propertyNames != null) {            propNamesLength = propertyNames.length;        }                for (int i = 0; i < attributes.getLength(); i++) {            String name = attributes.getLocalName(i);            if ("".equals(name)) {                name = attributes.getQName(i);            }            String value = attributes.getValue(i);                        // we'll now check for custom mappings            for (int n = 0; n<attNamesLength; n++) {                if (name.equals(attributeNames[n])) {                    if (n < propNamesLength) {                        // set this to value from list                        name = propertyNames[n];                                        } else {                        // set name to null                        // we'll check for this later                        name = null;                    }                    break;                }            }                         if (digester.log.isDebugEnabled()) {                digester.log.debug("[SetPropertiesRule]{" + digester.match +                        "} Setting property '" + name + "' to '" +                        value + "'");            }            if (!digester.isFakeAttribute(top, name)                     && !IntrospectionUtils.setProperty(top, name, value)                     && digester.getRulesValidation()) {                digester.log.warn("[SetPropertiesRule]{" + digester.match +                        "} Setting property '" + name + "' to '" +                        value + "' did not find a matching property.");            }        }    }
这部分源码的功能如下:

1、Object top = digester.peek();与之对应的是在ObjectCreateRule类的 begin(Attributes attributes)方法中会执行digester.push(instance);由于这里面实际存储的对象的结构是栈,且每个pattern会先执行ObjectCreateRule,再执行SetPropertiesRule;则这个函数得到的对象就是ObjectCreateRule类begin方法中实例化的对象。

2、得到对应的属性名称和属性值;(中间部分涉及到别名的处理,目前解析的过程中暂时用不到)

3、执行以下源码(这部分是解析重点):

            if (!digester.isFakeAttribute(top, name)                     && !IntrospectionUtils.setProperty(top, name, value)                     && digester.getRulesValidation()) {                digester.log.warn("[SetPropertiesRule]{" + digester.match +                        "} Setting property '" + name + "' to '" +                        value + "' did not find a matching property.");            }
先查看digester.isFakeAttribute(top, name)和digester.getRulesValidation()的源码:

    /**     * Determine if an attribute is a fake attribute.     */    public boolean isFakeAttribute(Object object, String name) {        if (fakeAttributes == null) {            return false;        }        List<String> result = fakeAttributes.get(object.getClass());        if (result == null) {            result = fakeAttributes.get(Object.class);        }        if (result == null) {            return false;        } else {            return result.contains(name);        }    }

    /**     * Return the rules validation flag.     */    public boolean getRulesValidation() {        return (this.rulesValidation);    }

其中的fakeAttributes和rulesValidation是我们在Catalina类的createStartDigester()中设置的:

        digester.setRulesValidation(true);        HashMap<Class, List<String>> fakeAttributes = new HashMap<Class, List<String>>();        ArrayList<String> attrs = new ArrayList<String>();        attrs.add("className");        fakeAttributes.put(Object.class, attrs);        digester.setFakeAttributes(fakeAttributes);

可以看出isFakeAttribute(Object object, String name)实际上是对属性进行检查;在之前Catalina类的createStartDigester()中设置了Object.class和attrs的对应关系,如果被检查的属性在attrs中设置过则返回true,不再执行SetPropertiesRule类后面的两个函数。

由此可以总结设置fakeAttribute其实就是设置需要过滤的属性,这些属性会与相应的类对应;如果被设置过滤,则在对类进行实例化是不会执行后面的属性赋值操作。

这里最重点的功能在IntrospectionUtils.setProperty(top, name, value)函数;它的作用是将解析xml所获取到的属性值,设置到之前ObjectCreateRule实例化的对象中,如果设置失败再进行digester.getRulesValidation()判断,判断是否打印属性值设置失败的日志。

IntrospectionUtils.setProperty(top, name, value)是功能的重点,查看其中源码:

    /**     * Find a method with the right name If found, call the method ( if param is     * int or boolean we'll convert value to the right type before) - that means     * you can have setDebug(1).     */    public static boolean setProperty(Object o, String name, String value) {    return setProperty(o,name,value,true);    }    public static boolean setProperty(Object o, String name, String value,boolean invokeSetProperty) {        if (dbg > 1)            d("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] = new Integer(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] = new Long(value);                            } catch (NumberFormatException ex) {                                ok = false;                            }                        // Try a setFoo ( boolean )                    } else if ("java.lang.Boolean".equals(paramType.getName())                            || "boolean".equals(paramType.getName())) {                        params[0] = new Boolean(value);                        // Try a setFoo ( InetAddress )                    } else if ("java.net.InetAddress".equals(paramType                            .getName())) {                        try {                            params[0] = InetAddress.getByName(value);                        } catch (UnknownHostException exc) {                            d("Unable to resolve host name:" + value);                            ok = false;                        }                        // Unknown type                    } else {                        d("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);                    }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) {            if (dbg > 0)                d("SecurityException for " + o.getClass() + " " + name + "="                        + value + ")");            if (dbg > 1)                ex1.printStackTrace();        } catch (IllegalAccessException iae) {            if (dbg > 0)                d("IllegalAccessException for " + o.getClass() + " " + name                        + "=" + value + ")");            if (dbg > 1)                iae.printStackTrace();        } catch (InvocationTargetException ie) {            if (dbg > 0)                d("InvocationTargetException for " + o.getClass() + " " + name                        + "=" + value + ")");            if (dbg > 1)                ie.printStackTrace();        }        return false;    }
这部分代码比较长,但执行的功能还是比较单一的,首先根据属性值获取到对应的set方法名称,然后根据反射找到所对应参数类型,执行set方法,或者通过setProperty方法设置属性值。总之,属性值在这里会被设置到之前实例化的对象中;例如server.xml解析时会将port="8005"的属性值设置到org.apache.catalina.core.StandardServer类的对象中。
到这里SetPropertiesRule的分析也结束了。


接下来是SetNextRule的源码:

在在Digester类中执行的SetNextRule的构造方法如下:

    /**     * Construct a "set next" rule with the specified method name.     *     * @param methodName Method name of the parent method to call     * @param paramType Java class of the parent method's argument     *  (if you wish to use a primitive type, specify the corresonding     *  Java wrapper class instead, such as <code>java.lang.Boolean</code>     *  for a <code>boolean</code> parameter)     */    public SetNextRule(String methodName,                       String paramType) {        this.methodName = methodName;        this.paramType = paramType;    }
在这里它覆盖的方法只有一个end()方法,内容如下:

    /**     * Process the end of this element.     */    public void end() throws Exception {        // Identify the objects to be used        Object child = digester.peek(0);        Object parent = digester.peek(1);        if (digester.log.isDebugEnabled()) {            if (parent == null) {                digester.log.debug("[SetNextRule]{" + digester.match +                        "} Call [NULL PARENT]." +                        methodName + "(" + child + ")");            } else {                digester.log.debug("[SetNextRule]{" + digester.match +                        "} Call " + parent.getClass().getName() + "." +                        methodName + "(" + child + ")");            }        }        // Call the specified method        IntrospectionUtils.callMethod1(parent, methodName,                child, paramType, digester.getClassLoader());                    }
通过我之前的文章介绍过end()方法是在读取xml结束标签时会触发执行的。现在里面的功能分析如下:

1、Object child = digester.peek(0);和Object parent = digester.peek(1);这两个分别是获取执行当前标签开始时所得到的实例化对象和父一级标签所对应的实例化对象。

2、Call the specified method。这里的methodName和paramType是调用SetNextRule的构造函数时的参数,具体的功能是通过反射将child 对象设置到parent 对象中;methodName和paramType代表在父类需要执行方法的名称和类型。

IntrospectionUtils.callMethod1内部的实现过程完全是基于反射的,且功能职责比较单一,比较容易理解,就不继续跟进了。

至于将child 对象设置到parent 对象的原因,是因为这与tomcat结构设计有关,每个子模块通过将自己注册到父模块进行管理,模块之间耦合度会很小,它们通过实现统一的接口保证行为的一致性。这部分会在以后的文章中进行继续分析。

而server.xml结构是tomca设计架构本身映射到配置文件中的结果;Server管理Service,Service管理Connector和Engine,Engine管理Host,等等。

所以这里会将child 对象设置到parent 对象中。


到这里分析完了三个比较重要的Rule,其它的Rule可以根据类内部实现的方法名称和解析xml所触发函数来确定其功能。


接下来就是分析digester.addRuleSet(RuleSet ruleSet)这部分的方法了。

RuleSet接口定义了getNamespaceURI(),和addRuleInstances(Digester digester)两个方法;

继续查看Digester类的addRuleSet(RuleSet ruleSet)源码:

    /**     * Register a set of Rule instances defined in a RuleSet.     *     * @param ruleSet The RuleSet instance to configure from     */    public void addRuleSet(RuleSet ruleSet) {        String oldNamespaceURI = getRuleNamespaceURI();        String newNamespaceURI = ruleSet.getNamespaceURI();        if (log.isDebugEnabled()) {            if (newNamespaceURI == null) {                log.debug("addRuleSet() with no namespace URI");            } else {                log.debug("addRuleSet() with namespace URI " + newNamespaceURI);            }        }        setRuleNamespaceURI(newNamespaceURI);        ruleSet.addRuleInstances(this);        setRuleNamespaceURI(oldNamespaceURI);    }
在这段源码中可以看到在执行ruleSet.addRuleInstances(this)之前,先将ruleSet之中的namespaceURI设置到Digester中,在执行之后会将原有的namespaceURI设置回来。

要理解这么做的原因先要看ruleSet.addRuleInstances(this)做了些什么。


我这里随便拿了一个EngineRuleSet类进行举例:

EngineRuleSet继承了RuleSetBase类,而RuleSetBase类是实现了RuleSet接口的抽象方法。

getNamespaceURI()方法在RuleSetBase中实现了,EngineRuleSet中主要实现了addRuleInstances(Digester digester)方法;与之前的ruleSet.addRuleInstances(this)相对应。

查看EngineRuleSet类的addRuleInstances(Digester digester)源码:

    /**     * <p>Add the set of Rule instances defined in this RuleSet to the     * specified <code>Digester</code> instance, associating them with     * our namespace URI (if any).  This method should only be called     * by a Digester instance.</p>     *     * @param digester Digester instance to which the new Rule instances     *  should be added.     */    public void addRuleInstances(Digester digester) {                digester.addObjectCreate(prefix + "Engine",                                 "org.apache.catalina.core.StandardEngine",                                 "className");        digester.addSetProperties(prefix + "Engine");        digester.addRule(prefix + "Engine",                         new LifecycleListenerRule                         ("org.apache.catalina.startup.EngineConfig",                          "engineConfigClass"));        digester.addSetNext(prefix + "Engine",                            "setContainer",                            "org.apache.catalina.Container");        //Cluster configuration start        digester.addObjectCreate(prefix + "Engine/Cluster",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties(prefix + "Engine/Cluster");        digester.addSetNext(prefix + "Engine/Cluster",                            "setCluster",                            "org.apache.catalina.Cluster");        //Cluster configuration end        digester.addObjectCreate(prefix + "Engine/Listener",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties(prefix + "Engine/Listener");        digester.addSetNext(prefix + "Engine/Listener",                            "addLifecycleListener",                            "org.apache.catalina.LifecycleListener");        digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));        digester.addObjectCreate(prefix + "Engine/Valve",                                 null, // MUST be specified in the element                                 "className");        digester.addSetProperties(prefix + "Engine/Valve");        digester.addSetNext(prefix + "Engine/Valve",                            "addValve",                            "org.apache.catalina.Valve");    }
走到这里,就会发现里面所有代码的逻辑功能都已经分析过了,只是设置的参数变了。增加了一类的以Engine为关键字的规则,当server.xml的层级规则符合这些设定规则时就会实例化相应的类,并设置相应的属性值等。

现在就可以具体分析addRuleSet(RuleSet ruleSet)中namespaceURI设置的原因了。

        setRuleNamespaceURI(newNamespaceURI);        ruleSet.addRuleInstances(this);        setRuleNamespaceURI(oldNamespaceURI);
由这段代码可知,ruleSet.addRuleInstances(this);执行过程中一定会有namespaceURI的赋值操作;

namespaceURI的赋值位置则需要跟进代码,例如EngineRuleSet里,根据前面的分析最后一定会走到Digester类的addRule(String pattern, Rule rule)中:

    public void addRule(String pattern, Rule rule) {        rule.setDigester(this);        getRules().add(pattern, rule);    }
这里执行的 getRules().add(pattern, rule)操作实际上是执行RulesBase类的add()方法,其源码如下:

    public void add(String pattern, Rule rule) {        // to help users who accidently add '/' to the end of their patterns        int patternLength = pattern.length();        if (patternLength>1 && pattern.endsWith("/")) {            pattern = pattern.substring(0, patternLength-1);        }                        List list = (List) cache.get(pattern);        if (list == null) {            list = new ArrayList();            cache.put(pattern, list);        }        list.add(rule);        rules.add(rule);        if (this.digester != null) {            rule.setDigester(this.digester);        }        if (this.namespaceURI != null) {            rule.setNamespaceURI(this.namespaceURI);        }    }
最后会为每个增加的rule设置对应namespaceURI;而且每个rule对应的namespaceURI可以不相同。

走到这里,关于tomcat的Rule规则就介绍完成了,至于其他未介绍的Rule或者RuleSet,其实原理完全都是一样的。




接下来会解释本文中遗留下来的问题:

Digester类的stack对象有什么用,它里面有什么?

关于stack,是一个栈结构,里面存放的是在解析server.xml过程中实例化的对象;

它的调用则需要从Catalina类的load()方法开始,以下是截取的代码片段:

            inputSource.setByteStream(inputStream);            digester.push(this);            digester.parse(inputSource);            inputStream.close();
由这里可知:

1、digester.push(this);会将当前对象放入stack中;

2、digester.push(this);在digester.parse(inputSource);之前执行,就代表后面解析xml所实例化的对象及操作,都在stack有值的基础上进行操作的。且这个基础的对象就是Catalina类的实例化对象。

继续查看push源码:

    /**     * Push a new object onto the top of the object stack.     *     * @param object The new object     */    public void push(Object object) {        if (stack.size() == 0) {            root = object;        }        stack.push(object);    }
这里的root对象实际就是Catalina类的实例化对象。


现在知道stack栈第一个对象是Catalina类的实例化对象,那么再结合之前分析过的SetNextRule类的end()方法;

当解析xml的第一个节点server的结束标签时,栈中只有两个对象:Catalina和StandardServer两个类的实例化对象;

当执行end()方法的过程中,实际上是执行Catalina类的setServer方法将StandardServer设置进去;

到这里就可以明白Catalina管理着StandardServer;

通过类似的方法也可以发现StandardServer管理着NamingResources、StandardService等;StandardService管理着Connector、StandardThreadExecutor、StandardEngine等等。

具体各服务组件之间的关系会在以后的文章中进行说明。

stack的作用基本就是这些。

最后在补一张Catalina的类图,方便更好的理解。








0 0