1. 程式人生 > >Tomcat7.0原始碼分析——server.xml檔案的載入與解析

Tomcat7.0原始碼分析——server.xml檔案的載入與解析

前言

作為Java程式設計師,對於Tomcat的server.xml想必都不陌生。本文基於Tomcat7.0的Java原始碼,對server.xml檔案是如何載入和解析進行分析。

載入過程分析

Bootstrap的load方法用於載入Tomcat的server.xml,實際是通過反射呼叫Catalina的load方法,程式碼如下:
    /**
     * Load daemon.
     */
    private void load(String[] arguments)
        throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method = 
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);

    }

Catalina的load方法實現如下:
    /**
     * Start a new server instance.
     */
    public void load() {

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed
        initNaming();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource("file://" + file.getAbsolutePath());
        } catch (Exception e) {
            // Ignore
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                // Ignore
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if( inputStream==null ) {
            try {
                inputStream = getClass().getClassLoader()
                .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                // Ignore
            }
        }
        
        if ((inputStream == null) && (file != null)) {
            log.warn("Can't load server.xml from " + file.getAbsolutePath());
            if (file.exists() && !file.canRead()) {
                log.warn("Permissions incorrect, read permission is not allowed on the file.");
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
            inputStream.close();
        } catch (Exception e) {
            log.warn("Catalina.start using "
                               + getConfigFile() + ": " , e);
            return;
        }

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                throw new java.lang.Error(e);
            else   
                log.error("Catalina.start", e);
            
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled())
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");

    }

這裡對上述程式碼進行分析:
1) initDirs方法用於對catalina.home和catalina.base的一些檢查工作。
2) initNaming方法給系統設定java.naming.factory.url.pkgs和java.naming.factory.initial。在建立JNDI上下文時,使用Context.INITIAL_CONTEXT_FACTORY("java.naming.factory.initial")屬性,來指定建立JNDI上下文的工廠類;Context.URL_PKG_PREFIXES("java.naming.factory.url.pkgs")用在查詢url中包括scheme方法id時建立對應的JNDI上下文,例如查詢"java:/jdbc/test1"等類似查詢上,即以冒號":"標識的shceme。Context.URL_PKG_PREFIXES屬性值有多個java 包(package)路徑,其中以冒號":"分隔各個包路徑,這些包路徑中包括JNDI相關實現類。當在JNDI上下文中查詢"java:"這類包括scheme方案ID的url時,InitialContext類將優先查詢Context.URL_PKG_PREFIXES屬性指定的包路徑中是否存在 scheme+"."+scheme + "URLContextFactory"工廠類(需要實現ObjectFactory介面),如果存在此工廠類,則呼叫此工廠類的getObjectInstance方法獲得此scheme方案ID對應的jndi上下文,再在此上下文中繼續查詢對應的url。
3) createStartDigester方法建立並配置將要用來啟動的Digester例項,並且設定一些列Rule,具體對映到server.xml。
4) 使用FileInputStream獲取conf/server.xml配置檔案輸入流。
5) 將FileInputStream封裝為InputSource,並且呼叫Digester的parse方法進行解析。
6) initStreams對輸出流、錯誤流重定向。

7) 初始化server,具體實現本文不做分析。

規則

在正式介紹Digester的parse方法的解析過程前,我們先來掌握一些規則相關的內容。Tomcat將server.xml檔案中的所有元素上的屬性都抽象為Rule,以Server元素為例,在記憶體中對應Server例項,Server例項的屬性值就來自於Server元素的屬性值。通過對規則(Rule)的應用,最終改變Server例項的屬性值。
Rule是一個抽象類,其中定義了以下方法:
  • getDigester:獲取Digester例項;
  • setDigester:設定Digester例項;
  • getNamespaceURI:獲取Rule所在的相對名稱空間URI;
  • setNamespaceURI:設定Rule所在的相對名稱空間URI;
  • begin(String namespace, String name, Attributes attributes):此方法在遇到一個匹配的XML元素的開頭時被呼叫,如:<Server>。
  • body(String namespace, String name, String text):在遇到匹配XML元素的body時,此方法被呼叫,如進入<Server>標籤內部時。
  • end(String namespace, String name):此方法在遇到一個匹配的XML元素的末尾時被呼叫。如:</Server>。
Rule目前有很多實現類,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等。下圖展示了Rule的部分實現類:

這裡以最常用的幾個規則表示Rule的類繼承體系,如下圖:

SAX(Simple API for XML)

相比於 DOM 而言 SAX 是一種速度更快,更有效,佔用記憶體更少的解析 XML 檔案的方法。它是逐行掃描,可以做到邊掃描邊解析,因此 SAX 可以在解析文件的任意時刻停止解析。SAX 是基於事件驅動的。SAX 不用解析完整個文件,在按內容順序解析文件過程中, SAX 會判斷當前讀到的字元是否符合 XML 檔案語法中的某部分。如果符合某部分,則會觸發事件。所謂觸發事件,就是呼叫一些回撥方法。在用 SAX 解析 xml 文件時候,在讀取到文件開始和結束標籤時候就會回撥一個事件,在讀取到其他節點與內容時候也會回撥一個事件。在 SAX 介面中,事件源是 org.xml.sax 包中的 XMLReader ,它通過 parser() 方法來解析 XML 文件,併產生事件。事件處理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 這 4 個介面。
事件處理器事件處理器處理的事件XMLReader 註冊方法 
ContentHanderXML 文件的開始與結束setContentHandler(ContentHandler h) 
DTDHander處理 DTD 解析setDTDHandler(DTDHandler h) 
ErrorHandler 處理 XML 時產生的錯誤setErrorHandler(ErrorHandler h) 
EntityResolver 處理外部實體 setEntityResolver(EntityResolver e)  

我們用來做內容解析的回撥方法一般都定義在 ContentHandler 介面中 。ContentHandler 介面常用的方法:
  • startDocument() :當遇到文件的開頭的時候,呼叫這個方法,可以在其中做一些預處理的工作。 
  • endDocument() :當文件結束的時候,呼叫這個方法,可以在其中做一些善後的工作。   
  • startElement(String namespaceURI, String localName,String qName, Attributes atts):當讀到開始標籤的時候,會呼叫這個方法。 namespaceURI 就是名稱空間, localName 是不帶名稱空間字首的標籤名, qName 是帶名稱空間字首的標籤名。通過 atts 可以得到所有的屬性名和相應的值。 
  • endElement(String uri, String localName, String name):在遇到結束標籤的時候,呼叫這個方法。
  • characters(char[] ch, int start, int length):這個方法用來處理在 XML 檔案中讀到的內容。例如: <high  data="30"/> 主要目的是獲取 high 標籤中的值。
使用 SAX 解析 XML 檔案一般有以下五個步驟: 
1 、建立一個 SAXParserFactory 物件; 
2 、呼叫 SAXParserFactory 中的 newSAXParser 方法建立一個 SAXParser 物件; 
3 、然後在呼叫 SAXParser 中的 getXMLReader 方法獲取一個 XMLReader 物件;
4 、例項化一個 DefaultHandler 物件;
5 、連線事件源物件 XMLReader 到事件處理類 DefaultHandler 中;
6 、呼叫 XMLReader 的 parse 方法從輸入源中獲取到的 xml 資料;
7 、通過 DefaultHandler 返回我們需要的資料集合。

解析過程分析

在介紹Catalina的load方法時,遇見了createStartDigester方法,它的實現如程式碼清單1:
程式碼清單1
    /**
     * 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,Digester繼承了DefaultHandler,而DefaultHandler預設實現了ContentHander、DTDHander、ErrorHandler及EntityResolver 這4個介面,程式碼如下:
public class DefaultHandler
    implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler

如果閱讀DefaultHandler的原始碼,發現它的所有實現都是空實現,看來要發揮解析作用,只能依靠Digester自己了,見程式碼清單2。
程式碼清單2
    @Override
    public void startDocument() throws SAXException {

        if (saxLog.isDebugEnabled()) {
            saxLog.debug("startDocument()");
        }

        configure();
    }

    @Override
    public void endDocument() throws SAXException {

        if (saxLog.isDebugEnabled()) {
            if (getCount() > 1) {
                saxLog.debug("endDocument():  " + getCount() +
                             " elements left");
            } else {
                saxLog.debug("endDocument()");
            }
        }

        while (getCount() > 1) {
            pop();
        }

        // Fire "finish" events for all defined rules
        Iterator<Rule> rules = getRules().rules().iterator();
        while (rules.hasNext()) {
            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;
            }
        }

        // Perform final cleanup
        clear();
    }

    @Override
    public void startElement(String namespaceURI, String localName,
                             String qName, Attributes list)
            throws SAXException {
        boolean debug = log.isDebugEnabled();
        
        if (saxLog.isDebugEnabled()) {
            saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
                    qName + ")");
        }
        
        // Parse system properties
        list = updateAttributes(list);
        
        // Save the body text accumulated for our surrounding element
        bodyTexts.push(bodyText);
        if (debug) {
            log.debug("  Pushing body text '" + bodyText.toString() + "'");
        }
        bodyText = new StringBuilder();

        // the actual element name is either in localName or qName, depending 
        // on whether the parser is namespace aware
        String name = localName;
        if ((name == null) || (name.length() < 1)) {
            name = qName;
        }

        // Compute the current matching rule
        StringBuilder sb = new StringBuilder(match);
        if (match.length() > 0) {
            sb.append('/');
        }
        sb.append(name);
        match = sb.toString();
        if (debug) {
            log.debug("  New match='" + match + "'");
        }

        // Fire "begin" events for all relevant rules
        List<Rule> rules = getRules().match(namespaceURI, match);
        matches.push(rules);
        if ((rules != null) && (rules.size() > 0)) {
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire begin() for " + rule);
                    }
                    rule.begin(namespaceURI, name, list);
                } catch (Exception e) {
                    log.error("Begin event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("Begin event threw error", e);
                    throw e;
                }
            }
        } else {
            if (debug) {
                log.debug("  No rules found matching '" + match + "'.");
            }
        }

    }


    @Override
    public void endElement(String namespaceURI, String localName,
                           String qName) throws SAXException {

        boolean debug = log.isDebugEnabled();

        if (debug) {
            if (saxLog.isDebugEnabled()) {
                saxLog.debug("endElement(" + namespaceURI + "," + localName +
                        "," + qName + ")");
            }
            log.debug("  match='" + match + "'");
            log.debug("  bodyText='" + bodyText + "'");
        }

        // Parse system properties
        bodyText = updateBodyText(bodyText);

        // the actual element name is either in localName or qName, depending 
        // on whether the parser is namespace aware
        String name = localName;
        if ((name == null) || (name.length() < 1)) {
            name = qName;
        }

        // Fire "body" events for all relevant rules
        List<Rule> rules = matches.pop();
        if ((rules != null) && (rules.size() > 0)) {
            String bodyText = this.bodyText.toString();
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire body() for " + rule);
                    }
                    rule.body(namespaceURI, name, bodyText);
                } catch (Exception e) {
                    log.error("Body event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("Body event threw error", e);
                    throw e;
                }
            }
        } else {
            if (debug) {
                log.debug("  No rules found matching '" + match + "'.");
            }
            if (rulesValidation) {
                log.warn("  No rules found matching '" + match + "'.");
            }
        }

        // Recover the body text from the surrounding element
        bodyText = bodyTexts.pop();
        if (debug) {
            log.debug("  Popping body text '" + bodyText.toString() + "'");
        }

        // Fire "end" events for all relevant rules in reverse order
        if (rules != null) {
            for (int i = 0; i < rules.size(); i++) {
                int j = (rules.size() - i) - 1;
                try {
                    Rule rule = rules.get(j);
                    if (debug) {
                        log.debug("  Fire end() for " + rule);
                    }
                    rule.end(namespaceURI, name);
                } catch (Exception e) {
                    log.error("End event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("End event threw error", e);
                    throw e;
                }
            }
        }

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

    }

程式碼清單1中建立完Digester後,會呼叫addObjectCreate、addSetProperties、addSetNext方法陸續新增很多Rule,這些方法的實現如程式碼清單3:
    public void addObjectCreate(String pattern, String className,
                                String attributeName) {
        addRule(pattern,
                new ObjectCreateRule(className, attributeName));
    }

    public void addSetProperties(String pattern) {
        addRule(pattern,
                new SetPropertiesRule());
    }

    public void addSetNext(String pattern, String methodName,
                           String paramType) {
        addRule(pattern,
                new SetNextRule(methodName, paramType));
    }

從上述程式碼我們看到這三個方法分別建立ObjectCreateRule、SetPropertiesRule及SetNextRule。為了簡化理解我們以Server相關的Rule為例,如程式碼清單4:
程式碼清單4
        digester.addObjectCreate("Server",
                                 "org.apache.catalina.core.StandardServer",
                                 "className");
        digester.addSetProperties("Server");
        digester.addSetNext("Server",
                            "setServer",
                            "org.apache.catalina.Server");

根據程式碼清單3的實現,我們知道最終會建立ObjectCreateRule、SetPropertiesRule及SetNextRule,並且呼叫addRule方法。addRule方法首先呼叫getRules方法獲取RulesBase,然後呼叫RulesBase的add方法。addRule方法的實現如下:
    public void addRule(String pattern, Rule rule) {

        rule.setDigester(this);
        getRules().add(pattern, rule);

    }


    public Rules getRules() {

        if (this.rules == null) {
            this.rules = new RulesBase();
            this.rules.setDigester(this);
        }
        return (this.rules);

    }

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<Rule> list = cache.get(pattern);
        if (list == null) {
            list = new ArrayList<Rule>();
            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);
        }

    }

其中,cache的資料結構為HashMap<String,List<Rule>>,每個鍵值維護一個List<Rule>,由此可知,對Server標籤來說,對應的Rule列表為ObjectCreateRule、SetPropertiesRule及SetNextRule。


Digester解析XML的入口是其parse方法,其處理步驟如下:
1.建立XMLReader ;
2.使用XMLReader解析XML。
parse方法的程式碼如下:

    public Object parse(InputSource input) throws IOException, SAXException {
 
        configure();
        getXMLReader().parse(input);
        return (root);

    }

getXMLReader方法呼叫getParser建立SAXParser ,然後呼叫SAXParser 的getXMLReader方法建立XMLReader ,程式碼如下:

    public XMLReader getXMLReader() throws SAXException {
        if (reader == null){
            reader = getParser().getXMLReader();
        }        
                               
        reader.setDTDHandler(this);           
        reader.setContentHandler(this);        
        
        if (entityResolver == null){
            reader.setEntityResolver(this);
        } else {
            reader.setEntityResolver(entityResolver);           
        }
        
        reader.setErrorHandler(this);
        return reader;
    }

getParser方法呼叫getFactory方法建立SAXParserFactory,然後呼叫SAXParserFactory的newSAXParser方法建立SAXParser ,程式碼如下:
    public SAXParser getParser() {

        // Return the parser we already created (if any)
        if (parser != null) {
            return (parser);
        }

        // Create a new parser
        try {
            parser = getFactory().newSAXParser();
        } catch (Exception e) {
            log.error("Digester.getParser: ", e);
            return (null);
        }

        return (parser);

    }

getFactory方法使用SAX的API生成SAXParserFactory例項,程式碼如下:

    public SAXParserFactory getFactory()
    throws SAXNotRecognizedException, SAXNotSupportedException,
    ParserConfigurationException {

        if (factory == null) {
            factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(namespaceAware);
            factory.setValidating(validating);
            if (validating) {
                // Enable DTD validation
                factory.setFeature(
                        "http://xml.org/sax/features/validation",
                        true);
                // Enable schema validation
                factory.setFeature(
                        "http://apache.org/xml/features/validation/schema",
                        true);
            }
        }
        return (factory);

    }

XMLReader解析XML時,會生成事件,回撥Digester的startDocument方法,解析的第一個元素是Server,此時回撥Digester的startElement方法,入參Attributes list即Server上的屬性,如port、shutdown等,入參qName即為Server。startElement方法的程式碼如下:
   @Override
    public void startElement(String namespaceURI, String localName,
                             String qName, Attributes list)
            throws SAXException {
        boolean debug = log.isDebugEnabled();
        
        if (saxLog.isDebugEnabled()) {
            saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
                    qName + ")");
        }
        
        // Parse system properties
        list = updateAttributes(list);
        
        // Save the body text accumulated for our surrounding element
        bodyTexts.push(bodyText);
        if (debug) {
            log.debug("  Pushing body text '" + bodyText.toString() + "'");
        }
        bodyText = new StringBuilder();

        // the actual element name is either in localName or qName, depending 
        // on whether the parser is namespace aware
        String name = localName;
        if ((name == null) || (name.length() < 1)) {
            name = qName;
        }

        // Compute the current matching rule
        StringBuilder sb = new StringBuilder(match);
        if (match.length() > 0) {
            sb.append('/');
        }
        sb.append(name);
        match = sb.toString();
        if (debug) {
            log.debug("  New match='" + match + "'");
        }

        // Fire "begin" events for all relevant rules
        List<Rule> rules = getRules().match(namespaceURI, match);
        matches.push(rules);
        if ((rules != null) && (rules.size() > 0)) {
            for (int i = 0; i < rules.size(); i++) {
                try {
                    Rule rule = rules.get(i);
                    if (debug) {
                        log.debug("  Fire begin() for " + rule);
                    }
                    rule.begin(namespaceURI, name, list);
                } catch (Exception e) {
                    log.error("Begin event threw exception", e);
                    throw createSAXException(e);
                } catch (Error e) {
                    log.error("Begin event threw error", e);
                    throw e;
                }
            }
        } else {
            if (debug) {
                log.debug("  No rules found matching '" + match + "'.");
            }
        }

    }

startElement方法的處理步驟如下:
1.match剛開始為空字串,拼接Server後變為Server。
2.呼叫RulesBase的match方法,返回cache中按照鍵值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。
3.迴圈列表依次遍歷ObjectCreateRule、SetPropertiesRule及SetNextRule,並呼叫它們的begin方法。

ObjectCreateRule的begin方法將生成Server的例項(預設為"org.apache.catalina.core.StandardServer",使用者可以通過給Server標籤指定className使用其它Server實現),最後將Server的例項壓入Digester的棧中,程式碼如下:

    @Override
    public void begin(String namespace, String name, 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);

    }

SetPropertiesRule的begin方法首先將剛才壓入棧中的Server例項出棧,然後給Server例項設定各個屬性值,如port、shutdown等,程式碼如下:
    @Override
    public void begin(String namespace, String theName, 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.");
            }
        }

    }

SetNextRule的begin不做什麼動作。當遇到Server的結束標籤時,還會依次呼叫ObjectCreateRule、SetPropertiesRule及SetNextRule的end方法,不再贅述。所有元素的解析都與Server標籤同理,最終將server.xml檔案中設定的元素及其屬性值,構造出Tomcat中的容器,如:Server、Service、Connector等。

後記:個人總結整理的《深入理解Spark:核心思想與原始碼分析》一書現在已經正式出版上市,目前京東、噹噹、天貓等網站均有銷售,歡迎感興趣的同學購買。