tomcat启动(三)Catalina分析-load方法分析

时间:2024-01-14 16:41:08

load()方法按从上到下顺序分析(主要分析本人所没学过的知识点,其它略过。。。)。

Digester类作用

使用sax技术对xml进行解析

未开始解析时Digester.push(this)这个用来为catalina设置server

Digester的stack对象栈中持有Catalina对象,

解析xml过程中需要用到的类:

Rule:这个类有很多子类,为解析时遇到不同的匹配模式pattern调用不同的处理动作即不同rule。

  当解析到开始标记时会调用其子类ObjectCreateRule.begin()方法.

  如解析到<Server>时就会创建StandardServer类的实例并反射调用Digester的stack栈顶对象的setter方法(调用的方法通过传入的name值确定)。

  IntrospectionUtils.setProperty(top, name, value)top栈顶对象,name要设置的属性名---方法名的一部分,value要设置的属性值

  刚开始时栈顶元素是Catalina,即调用Catalina.setServer(Server object)方法设置Server为后面调用Server.start()做准备

  然后将StandardServer对象实例放入Digester的stack对象栈中,

  如上面所示会调用setter方法设置NamingResourcesImpl、NamingContextListener。调用addService()方法设置service

  当解析到<service>时StandardService实例化,并设置StandardService的Connector、Executor:StandardThreadExecutor、Container

上面Server,Service,Executor,Container,Connector对象设置都是在解析过程中设置了。

A Digester processes an XML input stream by matching a series of element nesting
patterns to execute Rules that have been added prior to the start of parsing. This
package was inspired by the XmlMapper class that was part of Tomcat 3.0 and 3.1,
but is organized somewhat differently.
Digester通过匹配一系列元素嵌套模式来处理XML输入流,以执行在开始解析之前添加的规则。
该软件包受到作为Tomcat 3.0和3.1的一部分的XmlMapper类的启发,但是它的组织方式略有不同。

具体的对象设置过程请跳转查看:

Catalina.createDigester方法详细理解

 一、CreateStartDigester()//Digester的初始化,为Xml的标签即解析模式增加处理规则rule
二、org.xml.sax.InputSource.
XML实体的单一输入源
创建一个带有系统标识符的新输入源。
应用程序也可以使用setPublicId来包含公共标识符,
或者setEncoding来指定字符编码,如果已知的话。
如果系统标识符是URL,则必须完全解析(它可能不是相对URL)。
inputSource = new InputSource(file.toURI().toURL().toString());//这个很标准file.toURL()已经不推荐使用了,要获取这个文件路径必须用file.toURI()进行转换
/**如果还有一个指定的字符流,SAX解析器将忽略这一点,但它会使用一个字节流来优先打开一个URI连接本身。
如果应用程序知道字节流的字符编码,则应使用setEncoding方法进行设置。*/
inputSource.setByteStream(inputStream);

 三、digester解析xml文件

digester.push(this);将catalina类放入对象栈顶
digester.parse(inputSource);这里开始解析xml数据资源

上面load方法中知道inputSource是指catalina.base/conf/server.xml文件字节流

public Object parse(InputSource input) {

configure();//提供这个Digester实例的懒惰配置的钩子。默认实现什么都不做,但子类可以根据需要重写。
getXMLReader().parse(input);
return (root);

}

返回用于解析输入文档的XMLReader。 FIX ME:在JAXP / XERCES中有一个bug,阻止使用包含DTD的模式的解析器。

Digester.getXMLReader() 
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;
}

getParser()

创建一个SAXParser

通过getFactory()获得SAXParserFactory工厂类

工厂类调用newSAXParser()创建parser实例

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

得到XMLReader

SAXParser.getXMLReader() 返回由该类实现封装的org.xml.sax.XMLReader

getXMLReader方法中reader.setDTDHandler(this)注册DTD事件处理程序,将会监听SAXParser解析器报告的DTD事件

reader.setErrorHandler(this);注册错误事件处理程序

reader.setContentHandler(this); 

注册内容事件处理程序。如果应用程序没有注册内容处理程序,SAX解析器报告的所有内容事件将被默认忽略。

应用程序可以在解析的中间注册一个新的或不同的处理程序,而SAX解析器必须立即开始使用新的处理程序

在SAX编程中,需要为XMLReader设置相应的ContentHandler,
该Handler的startDocument,endDocument,startElement,endElement及characters等方法将用于响应解析xml时的标签事件,
可看到Digester继承于DefaultHandler类,而该类则实现ContentHandler接口,
因此在对server.xml解析时将相应地调用到Digester的startDocument,endDocument,startElement,endElement及characters等方法

最后返回一个xmlreader执行

XMLReader.parse(InputSource input)
XMLReader将通过注册的事件处理程序提供有关XML文档的信息。
此方法是同步的:它将不会返回,直到解析结束。
如果客户端应用程序想要尽早终止解析,则应该抛出异常
当开始解析xml文档时,会向ContentHandler发送内容事件,即Digester。
startDocument,endDocument,startElement,endElement及characters等方法将会被调用
在调用所有解析方法之前会将调用
Digester.setDocumentLocator(Locator locator)设置Locator,报告文档相关事件。如错误事件
这个Locator包含的文档的编码,可以在startDocument中用到
提供定位器:如果这样做,它必须通过调用此方法在调用ContentHandler接口中的任何其他方
法之前将定位器提供给应用程序。定位器允许应用程序确定任何文档相关事件的结束位置,即使
解析器没有报告错误。通常,应用程序将使用此信息来报告其自身的错误(例如与应用程序业务
规则不匹配的字符内容)
1、Digester.startDocument() 

处理通知文档的开头,这里主要设置的编码The encoding used by the source XMl document.

((DocumentProperties.Encoding) root).setEncoding(((Locator2) locator).getEncoding());

再开始startElement()方法之前会先调用

startPrefixMapping(String prefix, String namespaceURI)//命名空间前缀进入范围的通知处理方法

这个方法主要是用HashMap<String, ArrayStack<String>>

namespaces 变量以prefix为key,ArrayStack为值保存数据ArrayStack保存着namespaceURI

ArrayStack<String> stack = namespaces.get(prefix);
if (stack == null) {
stack = new ArrayStack<>();
namespaces.put(prefix, stack);
}
stack.push(namespaceURI);

SAX XML reader将自动替换元素和属性名称的前缀。

然而,有些情况下,当应用程序需要在字符数据或属性值中使用前缀时,它们无法安全地自动扩展; start / endPrefixMapping事件将信息提供给应用程序,以在必要时在这些上下文中扩展前缀。

startPrefixMapping事件都将在相应的startElement事件之前立即发生,并且所有endPrefixMapping事件都将在相应的endElement事件之后立即发生

2、Digester.startElement(String namespaceURI, String localName, String qName, Attributes list)

namespaceURI The Namespace URI, or the empty string if the element has no
        Namespace URI or if Namespace processing is not being performed.
命名空间URI,如果元素没有命名空间URI或未执行命名空间处理,则为空字符串
localName
    The local name (without prefix), or the empty string if Namespace processing is
    not being performed.
本地名称(无前缀),或为空字符串(如果命名空间处理) 没有执行。
qName   The qualified name (with prefix), or the empty string
     if qualified names are not available.
限定名称(带前缀)或为空字符串 如果限定名称不可用。
list   The attributes attached to the element. If
      there are no attributes, it shall be an empty Attributes object.
附加到元素的属性。如果没有属性,它将是一个空的Attributes对象。

到达XML元素的开始处理通知

 updateAttributes(list)更新属性中的系统值引用

格式为“$ {xxx}”的文本都将被系统属性中适当的值替换。

初始化标签体内容

bodyTexts用于周围元素的正文文本字符串缓冲区堆栈。The stack of body text string buffers for surrounding elements.

bodyText当前元素标签内的正文内容

获取路径名

将标签的路径字符赋给name。XML格式如:<Servers><Server><Server></Servers>,

当解析到<Server>标签时路径则为Servers/Server,在得到路径后又将该路径赋予match变量;

得到与标签体路径名匹配的Rule List,触发其begin方法

这部分内容是tomcat解析xml的重要部分

Rule.begin(String namespace, String name, Attributes attributes)

当遇到匹配的XML元素的开始时调用此方法。默认实现委托到不推荐使用的方法,而不使用命名空间和名称参数,以保留向后兼容性。

然后这个方法内调用Rule.begin(Attributes attributes) 开始匹配xml元素

3、Digester.characters(char[] buffer, int start, int length) 处理从XML元素的正文接收到的字符数据的通知。

buffer The characters from the XML document来自XML文档的字符

start Starting offset into the buffer开始偏移到缓冲区

length Number of characters from the buffer缓冲区中的字符数

bodyText.append(buffer, start, length);接受元素标签内的正文内容

4、Digester.endElement(String namespaceURI, String localName, String qName)处理到达的XML元素的结束标记的通知

将bodyText中的变量值替换,如标签体中有${catalina.base},会使用System.getProperties()提取系统catalina.base的属性值

获取与标签路径名匹配的Rule,触发其body及end方法

matches再startElement中被复值了

List<Rule> rules = getRules().match(namespaceURI, match);

matches.push(rules);

// Parse system properties
bodyText = updateBodyText(bodyText);
List<Rule> rules = matches.pop();
rule.body(namespaceURI, name, bodyText);
// Recover the body text from the surrounding element
  bodyText = bodyTexts.pop(); rule.end(namespaceURI, name); // Recover the previous match expression
int slash = match.lastIndexOf('/');
if (slash >= 0) {
match = match.substring(0, slash);
} else {
match = "";
}

执行完endElement()方法后元素标签解析完毕需要取消注册此前缀映射

Digester.endPrefixMapping(String prefix)就是处理的地方

ArrayStack<String> stack = namespaces.get(prefix);
stack.pop();
if (stack.empty())
namespaces.remove(prefix);

5、Digester.endDocument()

方法内容为:弹出所有还在栈中的对象,执行所有Rule的finish方法,执行clear方法清空相关堆栈(clear方法将Digester定义的变量设置为null,configure设置为false)


到这里xml解析完成。等有空再把上面缕缕