tomcat源码解析(二)——xml解析过程分析

时间:2021-08-20 22:38:03

阅读tomcat源码过程中,一定会遇到利用Digester解析xml的过程,而这部分对于刚开始研究源码的人来说并不容易理解。在我之前的文章中,也遗留了关于tomcat启动过程中解析server.xml的过程。在这篇文章中,我将解读tomcat启动时解析server.xml的过程来帮助更好的理解Digester的使用和源码。

首先,理解Digester首先需要理解SAX的使用,关于SAX相关的类在jre中rt.jar中,属于jdk自带且可以用于解析xml。SAX是一种事件方式驱动去进行解析xml,它通过逐行读取xml文件,并在解析各个部分时触发相应的事件,然后通过回调函数进行自己的事件处理。与DOM的方式不同,SAX解析的xml不会保存其结构和状态,没有前后父子的关系等。在tomcat的使用中,更多关注的是当读取一个属性节点时对于回调事件的触发,实现相应的类初始化操作。

SAX API中主要有四种处理事件的接口,它们分别是ContentHandler,DTDHandler, EntityResolver 和 ErrorHandler;对应的示例代码如下

            SAXParserFactory factory = SAXParserFactory.newInstance();

SAXParser saxParser = factory.newSAXParser();

File file = new File("D:/tomcat/conf/server.xml");

InputSource input = new InputSource(new FileInputStream(file));

XMLReader reader = saxParser.getXMLReader();

reader.setContentHandler(new UserContentHandler());
reader.setDTDHandler(new UserDTDHandler());
reader.setEntityResolver(new UserEntityResolver());
reader.setErrorHandler(new UserErrorHandler());


reader.parse(input);
通过SAXParserFactory.newInstance()进行实例化,获取SAXParser,并得到XMLReader;

在XMLReader中可以为自己需要监听的事件添加实现。

上面的UserContentHandler,UserDTDHandler,UserEntityResolver,UserErrorHandler分别实现了ContentHandlerDTDHandlerEntityResolverErrorHandler接口;

一般使用较多的是ContentHandler接口的setDocumentLocator、startDocument、endDocument、startElement及endElement、characters函数;会对应到读取文档,结束读取文档,读取节点等的触发事件。

关于更多使用SAX的细节,各位可以自己查看SAX源码或者API文档进行了解。


现在,我将结合tomcat解析server.xml的源码分析解析过程。

首先,我通过tomcat的Catalina类load()中digester.parse(inputSource)跟踪源码,查看其中的细节。

tomcat源码:

Digester的parse;

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

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

}
其中实际上是调用getXMLReader()的parse方法实现文件加载,重点看下getXMLReader(),其源码如下:

    /**
* Return the XMLReader to be used for parsing the input document.
*
* FIX ME: there is a bug in JAXP/XERCES that prevent the use of a
* parser that contains a schema with a DTD.
* @exception SAXException if no XMLReader can be instantiated
*/
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.setProperty(
"http://xml.org/sax/properties/lexical-handler", this);

reader.setErrorHandler(this);
return reader;
}
到这里就可以发现它与这篇文章第一部分代码有相似之处

reader.setContentHandler(new UserContentHandler());
reader.setDTDHandler(new UserDTDHandler());
reader.setEntityResolver(new UserEntityResolver());
reader.setErrorHandler(new UserErrorHandler());

只是其中的参数变成了this。


现在重新查看Digester类的继承关系,如下图,可以发现Digester实际上已经在祖父类DefaultHandler中实现了SAX API中主要四种处理事件接口

tomcat源码解析(二)——xml解析过程分析

所以当参数传入this后,回调函数实际上就是调用自身实现的SAX API接口方法。


现在问题来了,四个接口大概二十多个回调方法,我们没有精力全部跟踪,到底server.xml加载过程中到底触发了哪些事件呢?

现在我根据源码,写了一段测试代码,单独先加载server.xml,查看调用了哪些函数。具体源码如下(代码中的路径是我tomcat的server.xml的绝对路径):

package com.zpf.saxparserfactory.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;

public class Test {

public static void main(String[] args) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();

SAXParser saxParser = factory.newSAXParser();

File file = new File("D:/tomcat/conf/server.xml");

InputSource input = new InputSource(new FileInputStream(file));

XMLReader reader = saxParser.getXMLReader();

reader.setContentHandler(new UserContentHandler());
reader.setDTDHandler(new UserDTDHandler());
reader.setEntityResolver(new UserEntityResolver());
reader.setErrorHandler(new UserErrorHandler());
reader.setProperty(
"http://xml.org/sax/properties/lexical-handler", new UserLexicalHandler());

reader.parse(input);

} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}

private static class UserLexicalHandler implements LexicalHandler{

@Override
public void startDTD(String name, String publicId, String systemId) throws SAXException {
System.out.println("startDTD:" + publicId + "---" + systemId + "---" + name);

}

@Override
public void endDTD() throws SAXException {
System.out.println("endDTD:");

}

@Override
public void startEntity(String name) throws SAXException {
System.out.println("startEntity:" + name);

}

@Override
public void endEntity(String name) throws SAXException {
System.out.println("endEntity:" + name);

}

@Override
public void startCDATA() throws SAXException {
System.out.println("startCDATA:" );

}

@Override
public void endCDATA() throws SAXException {
System.out.println("endCDATA:" );

}

@Override
public void comment(char[] ch, int start, int length) throws SAXException {
//System.out.println("comment:" + new String(ch,start,length));//Digester的comment方法是空的

}

}
private static class UserErrorHandler implements ErrorHandler{

@Override
public void warning(SAXParseException exception) throws SAXException {
System.out.println("warning");

}

@Override
public void error(SAXParseException exception) throws SAXException {
System.out.println("error");

}

@Override
public void fatalError(SAXParseException exception) throws SAXException {
System.out.println("fatalError");

}

}
private static class UserEntityResolver implements EntityResolver{

@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
System.out.println("resolveEntity:" + publicId + "---" + systemId );
return null;
}

}
private static class UserDTDHandler implements DTDHandler{

@Override
public void notationDecl(String name, String publicId, String systemId) throws SAXException {
System.out.println("notationDecl:" + name + "---" + publicId + "----"+ systemId );
}

@Override
public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName)
throws SAXException {
System.out.println("unparsedEntityDecl:" + name + "---" + publicId + "----"+ systemId + "---" + notationName);
}

}
private static class UserContentHandler implements ContentHandler{

@Override
public void setDocumentLocator(Locator locator) {
System.out.println("setDocumentLocator:" +locator);

}

@Override
public void startDocument() throws SAXException {
System.out.println("startDocument:");

}

@Override
public void endDocument() throws SAXException {
System.out.println("endDocument:");
}

@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
System.out.println("startPrefixMapping:" +prefix + "---" + uri);

}

@Override
public void endPrefixMapping(String prefix) throws SAXException {
System.out.println("endPrefixMapping:" +prefix);

}

@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
System.out.println("startElement:" + uri + "---" + localName + "----"+ qName);
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("endElement:" + uri + "---" + localName + "----"+ qName);

}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
System.out.println("characters:" + new String(ch,start,length));

}

@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
System.out.println("ignorableWhitespace:" + new String(ch,start,length));
}

@Override
public void processingInstruction(String target, String data) throws SAXException {
System.out.println("processingInstruction:" + target + "---" + data);

}

@Override
public void skippedEntity(String name) throws SAXException {
System.out.println("skippedEntity:" + name);
}

}

}

代码功能很简单,就是打印在解析xml时打印所触发的回调函数的名称。

我的server.xml源文件:

<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">

<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
<Listener className="org.apache.catalina.core.JasperListener" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>

<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">

<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->


<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL HTTP/1.1 Connector on port 8080
-->
<Connector port="80" protocol="org.apache.coyote.http11.Http11NioProtocol"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
/>
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8000" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define a SSL HTTP/1.1 Connector on port 8443
This connector uses the JSSE configuration, when using APR, the
connector should be using the OpenSSL style configuration
described in the APR documentation -->
<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
-->

<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />


<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->

<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">

<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->

<!-- The request dumper valve dumps useful debugging information about
the request and response data received and sent by Tomcat.
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
-->

<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>

<!-- Define the default virtual host
Note: XML Schema validation will not work with Xerces 2.2.
-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">

<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->

<!-- Access log processes all example.
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
-->

</Host>
</Engine>
</Service>
</Server>
运行后,发现控制台打印characterst都是空的,characterst的API注释说明:Receive notification of character data(接受字符数据通知)。注释掉这个函数中的打印代码,重新执行;

打印结果:

tomcat源码解析(二)——xml解析过程分析

到这里可以看出实际上在执行server.xml加载时,所触发的回调函数分别是:

ContentHandler接口:setDocumentLocator、startDocument、endDocument、startElement、endElement以及被注释的characters方法;

LexicalHandler接口的comment方法也被执行了。


其他未提到的方法在解析server.xml时也可能会调用,而且如果解析的xml文件不同,则对应的回调方法也各不相同;这里只以tomcat默认server.xml配置为例做说明,其他的代码理解过程类似,读者可以自己推理。


现在知道了在使用SAX解析xml时所用到的回调方法以及执行顺序,则我们就可以结合Digester的源码针对性的阅读和理解。

首先查看Digester类的setDocumentLocator源码,只是普通的set方法,设置Locator,无需关注;

ContentHandler接口的characters之前看到其控制台打印的参数内容是空的,且源码中只是简单赋值操作,可无需关注。

LexicalHandler接口的comment方法在它的父类中DefaultHandler2中,实现方法是空的函数体,也可以忽略。

startDocument源码功能是设置configured状态,决定是否调用initialize()函数,这个初始化类默认没有内容,主要用于继承和重写,功能可以参考源码注释。

startDocument执行的configure()源码:

    /**
* <p>
* Provide a hook for lazy configuration of this <code>Digester</code>
* instance. The default implementation does nothing, but subclasses
* can override as needed.
* </p>
*
* <p>
* <strong>Note</strong> This method may be called more than once.
* Once only initialization code should be placed in {@link #initialize}
* or the code should take responsibility by checking and setting the
* {@link #configured} flag.
* </p>
*/
protected void configure() {

// Do not configure more than once
if (configured) {
return;
}

log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester");
saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");

// Perform lazy configuration as needed
initialize(); // call hook method for subclasses that want to be initialized once only
// Nothing else required by default

// Set the configuration flag to avoid repeating
configured = true;

}

按照执行顺序,现在可以重点关注的函数是startElement、endElement、endDocument;

Digester的startElement源码如下:

    /**
* Process notification of the start of an XML element being reached.
*
* @param namespaceURI The Namespace URI, or the empty string if the element
* has no Namespace URI or if Namespace processing is not being performed.
* @param localName The local name (without prefix), or the empty
* string if Namespace processing is not being performed.
* @param qName The qualified name (with prefix), or the empty
* string if qualified names are not available.\
* @param list The attributes attached to the element. If there are
* no attributes, it shall be an empty Attributes object.
* @exception SAXException if a parsing error is to be reported
*/
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 StringBuffer();

// 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
StringBuffer sb = new StringBuffer(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 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 = (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、list = updateAttributes(list);这个函数是将属性中所有"${xxx}"替换成对应的真实属性值,实质上是调用system.getProperty(key)完成的。

2、添加match;执行matches.push(rules);将rules列表保存在缓存中。

3、通过match匹配rules,并对匹配到的rule执行rule.begin(namespaceURI, name, list);方法。

关于Rule现在只需明白它通过匹配得到,在startElement中调用begin方法,后面会详细解释Rule的部分。


补充:

updateAttributes(Attributes list)的源码解读(读者略过这部分也不会有影响。。。可以直接看补充结束后的内容)

Digester的updateAttributes(Attributes list)源码

   /**
* Returns an attributes list which contains all the attributes
* passed in, with any text of form "${xxx}" in an attribute value
* replaced by the appropriate value from the system property.
*/
private Attributes updateAttributes(Attributes list) {

if (list.getLength() == 0) {
return list;
}

AttributesImpl newAttrs = new AttributesImpl(list);
int nAttributes = newAttrs.getLength();
for (int i = 0; i < nAttributes; ++i) {
String value = newAttrs.getValue(i);
try {
String newValue =
IntrospectionUtils.replaceProperties(value, null, source);
if (value != newValue) {
newAttrs.setValue(i, newValue);
}
}
catch (Exception e) {
// ignore - let the attribute have its original value
}
}

return newAttrs;

}
这里直接将文件中读取的属性值value和经过标签替换后的属性值newValue进行比较,如果不同则代表属性值里有标签,且被替换。

执行"${xxx}"标签替换工作的是IntrospectionUtils.replaceProperties(value, null, source);

关于第三个参数source,在Digester类定义如下:

    // ---------------------------------------------------------- Static Fields
private static class SystemPropertySource
implements IntrospectionUtils.PropertySource {
public String getProperty( String key ) {
return System.getProperty(key);
}
}

protected static IntrospectionUtils.PropertySource source[] =
new IntrospectionUtils.PropertySource[] { new SystemPropertySource() };

这里可以看到source对象默认长度为1,且里面的IntrospectionUtils.PropertySource对象只有一个getProperty( String key )方法,对应会返回System.getProperty(key)的值。

source定义是protected;意思是可以被继承,重新赋值,不过只有在扩展Digester类时才会关心。

在结合IntrospectionUtils.replaceProperties(value, null, source);内部的源码:

/**
* Replace ${NAME} with the property value
*/
public static String replaceProperties(String value, Hashtable staticProp,
PropertySource dynamicProp[]) {
if (value.indexOf("$") < 0) {
return value;
}
StringBuffer sb = new StringBuffer();
int prev = 0;
// assert value!=nil
int pos;
while ((pos = value.indexOf("$", prev)) >= 0) {
if (pos > 0) {
sb.append(value.substring(prev, pos));
}
if (pos == (value.length() - 1)) {
sb.append('$');
prev = pos + 1;
} else if (value.charAt(pos + 1) != '{') {
sb.append('$');
prev = pos + 1; // XXX
} else {
int endName = value.indexOf('}', pos);
if (endName < 0) {
sb.append(value.substring(pos));
prev = value.length();
continue;
}
String n = value.substring(pos + 2, endName);
String v = null;
if (staticProp != null) {
v = (String) ((Hashtable) staticProp).get(n);
}
if (v == null && dynamicProp != null) {
for (int i = 0; i < dynamicProp.length; i++) {
v = dynamicProp[i].getProperty(n);
if (v != null) {
break;
}
}
}
if (v == null)
v = "${" + n + "}";

sb.append(v);
prev = endName + 1;
}
}
if (prev < value.length())
sb.append(value.substring(prev));
return sb.toString();
}
其实里面大部分是对标签格式分析的处理,值得关注的是:

               if (v == null && dynamicProp != null) {
for (int i = 0; i < dynamicProp.length; i++) {
v = dynamicProp[i].getProperty(n);
if (v != null) {
break;
}
}
}
里面的v = dynamicProp[i].getProperty(n);实际上就是执行传入参数source[i]对象的getProperty(key)方法,由于source长度为1,所以就是执行System.getProperty(key);

结论:updateAttributes(Attributes list)功能是对属性中${xxx}标签格式的替换,Digester类默认是执行System.getProperty(key)完成替换属性的。

补充结束---------------------


接下来安照顺序是Digester的endElement源码如下:

    /**
* Process notification of the end of an XML element being reached.
*
* @param namespaceURI - The Namespace URI, or the empty string if the
* element has no Namespace URI or if Namespace processing is not
* being performed.
* @param localName - The local name (without prefix), or the empty
* string if Namespace processing is not being performed.
* @param qName - The qualified XML 1.0 name (with prefix), or the
* empty string if qualified names are not available.
* @exception SAXException if a parsing error is to be reported
*/
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 rules = (List) matches.pop();
if ((rules != null) && (rules.size() > 0)) {
String bodyText = this.bodyText.toString();
for (int i = 0; i < rules.size(); i++) {
try {
Rule 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 = (StringBuffer) 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 = (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 = "";
}

}
endElement的源码主要功能如下:

1、将bodyText中所有"${xxx}"替换成对应的真实属性值,实际也是调用system.getProperty(key)完成的。(在startElement和endElement函数执行期间会调用characters赋值给bodyText,之前观察到characters得到的都是空字符串);

2、从matches中获取之前匹配到的rules,并在后续中使用。

3、为得到的rules分别执行rule.body(namespaceURI, name, bodyText);

4、为得到的rules分别执行rule.end(namespaceURI, name);

5、更新match,相当于返回到与之对应startElement之前的match;


然后是Digester的endDocument的源码:

/**
* Process notification of the end of the document being reached.
*
* @exception SAXException if a parsing error is to be reported
*/
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 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;
}
}

// Perform final cleanup
clear();

}
endDocument的源码功能包括:

1、清空stack对象的内容;(什么时候放入,什么时候使用这部分以后会说明)

2、获取所有的rules,并分别执行 rule.finish();

3、清空Digester对象的所有状态


到这里,关于tomcat解析xml的过程就已经分析完毕了;


但是对于Digester类的源码分析还没有结束,关于rule,总结出涉及到方法包括:

rule.begin(namespaceURI, name, list);

rule.body(namespaceURI, name, bodyText);

rule.end(namespaceURI, name);

rule.finish();

还有遗留问题:通过match匹配对应的rules,stack对象的作用

虽然这部分还是属于Digester类的职能范围,但是与解析xml的关联已不大。

了解rule需要从Catalina的load()方法的Digester digester = createStartDigester()开始跟踪;

这部分我会在以后的文章中进行分析。



由于刚开始写文章,欢迎各位指出文章中错误,也可以留言需要细致讲解的部分