阅读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分别实现了ContentHandler,DTDHandler,EntityResolver,ErrorHandler接口;
一般使用较多的是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中主要的四种处理事件接口。
所以当参数传入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