Tomcat源碼分析——SERVER.XML文件的加載與解析


前言

  作為Java程序員,對於Tomcat的server.xml想必都不陌生。本文基於Tomcat7.0的Java源碼,對server.xml文件是如何加載和解析的進行分析。

加載

 server.xml也是文件,Tomcat加載它會不會有什么不同的實現呢?

Bootstrap的load方法是加載Tomcat的server.xml的入口,load方法實際通過反射調用catalinaDaemon(類型為Catalina)的load方法,見代碼清單1。

代碼清單1

/**
 * 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);

}

順便向所有熟悉Tomcat的開發者問個問題,為什么Tomcat要用反射來調用catalinaDaemon的load方法,而不是采用catalinaDaemon.load()的方式呢?有知道的人希望通過短消息或者回復,告訴我,我將不勝感激。

拋開這個問題,繼續講解server.xml的加載,代碼清單1使用反射調用了Catalina的load方法,load方法的實現見代碼清單2。

代碼清單2

/**
 * Start a new server instance.
 */
public void load() {

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed jiaan

    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");

}

這里對代明清單2進行分析,其執行步驟如下:
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,具體實現本文不做分析。

我們看到Tomcat加載server.xml配置文件的方式,非常傳統,正是使用FileInputStream進行加載的。

解析

  當加載server.xml配置文件到內存后,開始對XML文件中的內容進行解析,主要包含兩個步驟:

  1. 構造server.xml的規則,這些規則即可以用於構造Tomcat內部的容器(如StandardServer,StandardService等),也可以對server.xml進行合法性檢查。如果server.xml不符合Tomcat內置的規則,在解析時將拋出異常,進而導致Tomcat無法啟動。
  2. 使用SAX解析server.xml,邊解析邊應用規則,最終使用server.xml中的配置構建好Tomcat所需的各種容器。

 server.xml解析的整個過程可以用圖1來表示。

圖1   server.xml解析過程

規則

  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時,此方法被調用,如進入標簽內部時;
  • end(String namespace, String name):此方法在遇到一個匹配的XML元素的末尾時被調用。如:</Server>;
    Rule目前有很多實現類,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等,圖2展示了Rule的部分實現類。
    Rule_

圖2  Rule的部分實現類

這里以最常用的幾個規則表示Rule的類繼承體系,如圖3所示。

Rule_

圖3  Rule的類繼承體系

 

SAX

  相比於 DOM 而言 SAX(Simple API for XML) 是一種速度更快,更有效,占用內存更少的解析 XML 文件的方法。它是逐行掃描,可以做到邊掃描邊解析,因此 SAX 可以在解析文檔的任意時刻停止解析。SAX 是基於事件驅動的。SAX 不用解析完整個文檔,在按內容順序解析文檔過程中, SAX 會判斷當前讀到的字符是否符合 XML 文件語法中的某部分。如果符合某部分,則會觸發事件。所謂觸發事件,就是調用一些回調方法。在用 SAX 解析 xml 文檔時候,在讀取到文檔開始和結束標簽時候就會回調一個事件,在讀取到其他節點與內容時候也會回調一個事件。在 SAX 接口中,事件源是 org.xml.sax 包中的 XMLReader ,它通過 parser() 方法來解析 XML 文檔,並產生事件。事件處理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 這 4 個接口。表1列出了這些事件處理器的處理的事件及注冊方法。

表1

事件處理器 事件處理器處理的事件 XMLReader 注冊方法
ContentHander XML 文檔的開始與結束 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 標簽中的值。

  使用 SAX 解析 XML 文件一般包括以下步驟: 

  1. 創建一個 SAXParserFactory 對象; 
  2. 調用 SAXParserFactory 中的 newSAXParser 方法創建一個 SAXParser 對象; 
  3. 然后在調用 SAXParser 中的 getXMLReader 方法獲取一個 XMLReader 對象;
  4. 實例化一個 DefaultHandler 對象;
  5. 連接事件源對象 XMLReader 到事件處理類 DefaultHandler 中;
  6. 調用 XMLReader 的 parse 方法從輸入源中獲取到的 xml 數據;
  7. 通過 DefaultHandler 返回我們需要的數據集合。

源碼分析

構造server.xml的規則

  前面在介紹Catalina的load方法時,遇見了createStartDigester方法,它的實現如代碼清單3。

代碼清單3

/**
 * 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);

}

代碼清單3首先創建Digester,Digester繼承了DefaultHandler,而DefaultHandler默認實現了ContentHander、DTDHander、ErrorHandler及EntityResolver 這4個接口,代碼如下:

public class DefaultHandler
implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler

如果閱讀DefaultHandler的源碼,發現它的所有實現都是空實現,看來要發揮解析作用,只能依靠Digester自己了。Digester實現了以上接口中的方法,見代碼清單4。

代碼清單4

@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 = "";
    }

}

代碼清單3在創建完Digester后,會多次調用addObjectCreate、addSetProperties和addSetNext方法陸續添加很多Rule,這些方法的實現如代碼清單5。

代碼清單5

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));

}

 從代碼清單5,我們看到這三個方法分別創建ObjectCreateRule、SetPropertiesRule及SetNextRule。為了簡化理解我們以Server相關的Rule為例,如代碼清單6所示。

代碼清單6

    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");

根據代碼清單5的實現,我們知道最終會創建ObjectCreateRule、SetPropertiesRule及SetNextRule,並且調用addRule方法將這些規則添加到Digester中。addRule方法首先調用getRules方法獲取RulesBase,然后調用RulesBase的add方法。addRule方法的實現見代碼清單7。

代碼清單7

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方法的實現見代碼清單8。

代碼清單8

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,由此可知,對Server標簽來說,對應的Rule列表為ObjectCreateRule、SetPropertiesRule及SetNextRule。

使用SAX解析server.xml

  Digester解析XML的入口是其parse方法(見代碼清單9),其處理步驟如下:

  1. 創建XMLReader ;
  2. 使用XMLReader解析XML。

 代碼清單9

public Object parse(InputSource input) throws IOException, SAXException {

    configure();
    getXMLReader().parse(input);
    return (root);

}

parse方法調用了getXMLReader,getXMLReader方法的實現見代碼清單10。

代碼清單10

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;
}

從代碼清單10看到,getXMLReader方法首先調用getParser創建SAXParser,然后調用SAXParser 的getXMLReader方法創建XMLReader。

getParser方法的實現見代碼清單11。

 代碼清單11

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);

}

從代碼清單11看到,getParser方法首先調用getFactory方法創建SAXParserFactory,然后調用SAXParserFactory的newSAXParser方法創建SAXParser。getFactory方法(見代碼清單12)使用SAX的API生成SAXParserFactory實例。

代碼清單12

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方法的實現見代碼清單13。

代碼清單13

@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方法(見代碼清單14)將生成Server的實例(默認為"org.apache.catalina.core.StandardServer",用戶可以通過給Server標簽指定className使用其它Server實現),最后將Server的實例壓入Digester的棧中。

代碼清單14

@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 jiaan.gja
    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance = clazz.newInstance();
    digester.push(instance);

}

  SetPropertiesRule的begin方法(見代碼清單15)首先將剛才壓入棧中的Server實例出棧,然后給Server實例設置各個屬性值,如port、shutdown等。

代碼清單15

@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等。

 

如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4866009.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM