Tomcat源码分析——server.xml文件的解析

前言

《Tomcat源码分析——server.xml文件的加载》一文中我们介绍了server.xml的加载,本文基于Tomcat7.0的Java源码,接着对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元素的开头时被调用,如:。
body(String namespace, String name, String text):在遇到匹配XML元素的body时,此方法被调用,如进入标签内部时。
end(String namespace, String name):此方法在遇到一个匹配的XML元素的末尾时被调用。如:。
Rule目前有很多实现类,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等。下图展示了Rule的部分实现类:
Rule_
这里以最常用的几个规则表示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 注册方法 |
| --------------------- | ------------------------------- | --------------------------------------------------- |
| 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 返回我们需要的数据集合。

源码分析

《Tomcat源码分析——server.xml文件的加载》一文中介绍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

@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:

    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>,每个键值维护一个List,由此可知,对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等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值