tomcat源碼解析(三)——Digester類源碼解析及Rule分析

在這篇文章中主要針對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的類圖,方便更好的理解。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章