Digester方法详解:

  1. 通读Digester之前先分析下他的结构:

    1.1该类继承了方法DefaultHandler2,DefaultHandler2继承了DefaultHandler是和sax解析器配合使用的类。当sax在对字符流进行加工的时候会根据实际情况调用 DefaultHandler中的方法。其使用的设计模式为模板模式。

          1.2createStartDigester方法中addObjectCreate、addSetNext的参数最终都是继承自抽象类Rule;方法addRuleSet的参数继承自抽象类RuleSetBase,实现接口RuleSet
    
          1.3总结:理解digester前需要了解的接口和抽象类:DefaultHandler、Rule、RuleSet。
  2. DefaultHandler类:

    DefaultHandler类中的方法:processingInstruction、ignorableWhitespace、characters、endElement、startElement、endPrefixMapping、startPrefixMapping、endDocument、startDocument、setDocumentLocator、resolveEntity等方法。上面所列出的大多为digester中需要用到的

  3. Rule类:主要四个方法 begin 、body、end、finish

  4. RuleSet类:addRuleInstances

  5. 关系梳理:

    (1)catalina类的load方法中两个主要的createStartDigester和digester.parse方法。我们先理解parse方法;createStartDigester方法到后面再说。

(2) digester.parse()是用sax来解析xml的。

         2.1根据其使用的模板设计模式,会在  解析xml节点时调用DefaultHandler类中方法。当调用startDocument时会   调用rule.begin。代码如下:该处的rule使用的是接口,实际调用中,是使用的那个实现类,要看rules.get()拿出的对象是什么。这个在后面学习createStartDigester会再提到。

[java] view plain copy

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 + "'.");
}
} }
2.2其余的方法endDocument调用rule.finish、endElement调用rule.body方法等就不在一一列举,代码中很清楚。

[java] view plain copy

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

}

(3)createStartDigester方法中会创建一些对象的实例,并调用其中的一些方法。用的方式大多为java中的反射机制。

  3.1其中addObjectCreate会创建ObjectCreateRule对象,该对象继承自Rule方法,它实现了rule的begin方法,在其中创建了类的实例。

[java] view plain copy

public void begin(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);

}

3.2 addSetProperties主要是解析xml节点中的属性值,实现了rule接口的begin方法。

[java] view plain copy

public void begin(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.");
}
}

}

3.3 addSetNext方法会创建一个SetNextRule对象,该对象实现了rule的end方法,并在其中调用了指定类中的指定方法。

[java] view plain copy

public void end() throws Exception {

// Identify the objects to be used
Object child = digester.peek(0);
Object parent = digester.peek(1);
if (digester.log.isDebugEnabled()) {
if (parent == null) {
digester.log.debug("[SetNextRule]{" + digester.match +
"} Call [NULL PARENT]." +
methodName + "(" + child + ")");
} else {
digester.log.debug("[SetNextRule]{" + digester.match +
"} Call " + parent.getClass().getName() + "." +
methodName + "(" + child + ")");
}
} // Call the specified method
IntrospectionUtils.callMethod1(parent, methodName,
child, paramType, digester.getClassLoader());

}

3.4 addRule方法就是在规则列表中,简单的添加一条规则。不同的是,实现的方法是rules接口中的add方法。添加的规则有:ConnectorCreateRule、SetAllPropertiesRule、SetParentClassLoaderRule。其中ConnectorCreateRule就是创建一个connector。

[java] view plain copy

public void addRule(String pattern, Rule rule) {

rule.setDigester(this);
getRules().add(pattern, rule);

}

RulesBase中的方法:

[java] view plain copy

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 list = (List) cache.get(pattern);
if (list == null) {
list = new ArrayList();
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);
}

}

              3.5addRuleSet方法添加的都是继承自RuleSet的对象,重写了方法addRuleInstances的逻辑。从代码中可以清晰的看到,添加的是tomcat中的engin、host等信息。

[java] view plain copy

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

在对象的实现中,addObjectCreate目的是创建对象,setNextSet是调用该对象的指定方法。比如NamingRuleSet对象实现Ejb、Environment、LocalEjb、Resource、Transaction等的初始化动作。EngineRuleSet对象实现Engine和其下的Cluster、Listener、Valve的初始化动作。HostRuleSet对象实现Host和其下的Alias、Cluster、Listener、Valve的初始化。ContextRuleSet对象主要实现Context、Listener、WebappLoader、Manager、Store、Parameter、Resources、Valve等的初始化。

  1. 总结:

    读懂digester,关键是需要理解DefaultHandler、Rule、RuleSet这三个抽象类和接口的关联关系,了解其使用的设计模式,将其串联起来就容易了。我在读代码时,最难理解的是,不知道为何一句parse就能把server.xml中的配置给加载完了,最后通过学习别人的文章后,逐渐弄懂了。以此记录,继续学习。

关于Digester何时调用startElement方法,我目前还没搞懂。但是从调用堆栈可以推测一下。

    "main@1" prio=5 tid=0x1 nid=NA runnable  java.lang.Thread.State: RUNNABLE
at org.apache.tomcat.util.digester.Digester.startElement(Digester.java:1120)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:509)
at com.sun.org.apache.xerces.internal.impl.dtd.XMLDTDValidator.startElement(XMLDTDValidator.java:746)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1394)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$ContentDriver.scanRootElementHook(XMLDocumentScannerImpl.java:1251)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:3058)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:820)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:601)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:531)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:887)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:823)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:639)
at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1419)
at org.apache.catalina.startup.Catalina.load(Catalina.java:601)
at org.apache.catalina.startup.Catalina.load(Catalina.java:652)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:564)
at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:309)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:492)

Tomcat源码学习(3)—— Digester介绍的更多相关文章

  1. Tomcat源码学习(1)

    Tomcat源码学习(1) IntelliJ IDEA 17.3.3 导入 Tomcat 9.0.6源码 下载源码 tomcat_9.0.6 启动 IDEA. 点击 Open,选择刚才下载的文件解压后 ...

  2. Tomcat源码学习

    Tomcat源码学习(一) 转自:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-26309 ...

  3. 【Tomcat源码学习】-1.概述

    Tomcat是用java语言开发的一个Web服务器,最近花了差不多两周时间对Tomcat 9.0源码进行了一遍学习,由于知识储备有限,也只是理解了一个大概,下面就由我来给大家分享一下我对Tomcat的 ...

  4. Tomcat源码学习记录--web服务器初步认识

    Tomcat作为开源的轻量级WEB服务器,虽然不是很适合某些大型项目,但是它开源,读其源代码可以很好的提高我们的编程功底和设计思维.Tomcat中用到了很多比较好的设计模式,其中代码风格也很值得我们去 ...

  5. Tomcat源码解析-整体流程介绍

    一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...

  6. Tomcat源码学习 - 环境搭建

    一. 源码下载 PS: 多图预警 在开始阅读源码之前,我们需要先构建一个环境,这样才能便于我们对源码进行调试,具体源码我们可以到官网进行下载(这里我以8.5.63版本为例). 二. 项目导入 下载并解 ...

  7. Tomcat源码学习一

    这段时间工作不太忙,所以抽时间学习了TOMCAT, TOMCAT实际就是负责保持TCP连接传递到部署的项目中.浏览器实质就是TCP发送器.将用户的请求封装成TCP发送请求.当然格式是双方协定的.使用的 ...

  8. Dubbo源码学习之-SPI介绍

    前言 学习之路还是要戒骄戒躁,一以贯之的积累前行.之前的公司部门技术达人少,自己总向往那些技术牛人多的团队,想象自己进去之后能跟别人学到多少东西.如今进到一个这样的团队之后,却发现之前自己的想法过于幼 ...

  9. 【Tomcat源码学习】-5.请求处理

    前四章节,主要对Tomcat启动过程中,容器加载.应用加载.连接器初始化进行了相关的原理和代码流程进行了学习.接下来开始进行接受网络请求后的相关处理学习.   一.整体流程      基于上一节图示进 ...

随机推荐

  1. 1054. [HAOI2008]移动玩具【BFS】

    Description 在一个4*4的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动 时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩具,请你用最少的移 ...

  2. 使用yii的layout,加入<?php echo $content; ?>这句话时,它会自动在子页面上面添加一个div包裹

    使用yii的layout,加入<?php echo $content; ?>这句话时,它会自动在子页面上面添加一个div包裹,而且div的id命名为id=content,这个和已有id重复 ...

  3. 在yii中使用memcache

    yii中可以很方便的使用memcache 一.配置在main.php的components中加入cache配置 array( 'components'=>array( 'cache'=>a ...

  4. 【原创】大叔经验分享(52)ClouderaManager修改配置报错

    Cloudera Manager中修改配置可能报错: Incorrect string value: '\xE7\xA8\x8B\xE5\xBA\x8F...' for column 'MESSAGE ...

  5. virtualbox+vagrant学习-2(command cli)-21-vagrant up命令

    Up 格式: vagrant up [options] [name|id] 这个命令根据你的Vagrantfile文件创建和配置客户机. 这是“vagrant”中最重要的一个命令,因为它是创建任何va ...

  6. sql查询以及常见问题理解解析

    推荐w3挺好的基础网站:https://www.w3cschool.cn/t_sql/t_sql_date_functions.html =,<,>,> =,<=,IN,BET ...

  7. 配虚拟ip脚本

    cat /home/master/init_pandora.sh #! /bin/shuser=`whoami`if [ $user = 'master' ]then sudo /sbin/ifcon ...

  8. Linux下如何查看分区文件系统类型

    1,fdisk -l fdisk -l 只能列出硬盘的分区表.容量大小以及分区类型,但看不到文件系统类型. 2,df -h df 命令是用来查看文件系统磁盘空间使用量的.但df 命令只会列出已挂载的文 ...

  9. 在windows上安装不同(两个)版本的Mysql数据库

    1.起因: 需要导入一个sql文件,发现死活导不进去.当执行到这一句时,就有问题.经过一番搜索,原来是我的数据库版本(原先Mysql版本5.5)低了,而支持该语句的版本应该是至少要5.7.那我索性就去 ...

  10. Fiddler设置代理后,iphone手机无法访问网络的全面解决办法

    好久不抓手机包,都有些忘记了,电脑上软件都在,原本是Fiddler4,现在已自动升级到V5. 入主题吧,首先Fiddler设置好代理,然后手机安装证书好了,设置手机代理,结果iphone无法访问网络, ...