什么是digester?解决了什么问题?

在tomcat里,digester就是一个解析xml的工具,用来解析server.xml来创建应用服务器
digester本来仅仅是Jakarta Struts中的一个工具,用于处理struts-config.xml配置文件。由于将XML文件转换成相应的Java对象是一项很通用的功能,后来又被移到了Apache Common项目中。但在Tomcat中并不依赖Apache common,而是将Digester源码包含了进来,并改变了包路径。
digester底层以来SAX(Simple API for XML),采用事件驱动的方式将xml配置转换为java对象,是对SAX的高层次封装。

SAX是什么?

SAX是一个事件驱动的xml解析框架,与之类似的xml解析框架还有DOM、JDOM、DOM4J等。SAX解析器不像DOM那样建立一个完整的文档树,而是在读取文档时激活一系列事件,这些事件被推给事件处理器,然后由事件处理器提供对文档内容的访问。
常见的事件处理器有三种基本类型:

  • 用于访问XML DTD内容的DTDHandler;
  • 用于低级访问解析错误的ErrorHandler;
  • 用于访问文档内容的ContentHandler,这也是最普遍使用的事件处理器,一般也是最关注的

例如由如下xml,xml的一个element可以可以包含三种内容

  • attribute。department包含name和code属性,user也包含name和code属性
  • 子element。department包含user、extension子element,extension包含property-name、property-value两个子element
  • body。property-name和property-value具有body,分别是director、joke
<department name="deptname001" code="deptcode001"><user name="username001" code ="usercode001"/><user name="username002" code ="usercode002"/><extension><property-name>director</property-name><property-value>joke</property-value></extension>
</department>

ContentHandler是一个接口,解析xml遇到各种事件时,会调用ContentHandler的相应回调接口,其中以下五个方法比较关注

  • startDocument 。开始解析xml的回调,在这里可以处理一些资源准备的工作
  • endDocument。xml解析结束的回调,在这里可以处理资源回收工作
  • startElement。读取到element开始时的回调,atts是该element内携带的属性
  • endElement。element读取结束的回调
  • characters。读取到body的回调

下面实现一个ContentHandler(删除了空实现的方法),来处理上面的xml

public class LoggerContentHandler implements ContentHandler {public static void main(String[] args) throws Exception {SAXParser parser = SAXParserFactory.newInstance().newSAXParser();parser.getXMLReader().setContentHandler(new LoggerContentHandler());parser.getXMLReader().parse(new InputSource(Files.newInputStream(Paths.get("./test.xml"))));}@Overridepublic void startDocument() throws SAXException {System.out.println("startDocument");}@Overridepublic void endDocument() throws SAXException {System.out.println("endDocument");}@Overridepublic void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {Map<String, String> attrMap = new HashMap<>();for (int i = 0; i < atts.getLength(); i++) {String name = atts.getLocalName(i);if (name.isEmpty()) {name = atts.getQName(i);}String value = atts.getValue(i);attrMap.put(name, value);}System.out.println("startElement, uri: " + uri + ", localName: " + localName + ", qName: " + qName +", atts: " + attrMap);}@Overridepublic void endElement(String uri, String localName, String qName) throws SAXException {System.out.println("endElement, uri: " + uri + ", localName: " + localName + ", qName: " + qName);}@Overridepublic void characters(char[] ch, int start, int length) throws SAXException {System.out.println("characters: " + new String(ch, start, length));}
}

输出如下

startDocument
startElement, uri: , localName: , qName: department, atts: {code=deptcode001, name=deptname001}
characters: startElement, uri: , localName: , qName: user, atts: {code=usercode001, name=username001}
endElement, uri: , localName: , qName: user
characters: startElement, uri: , localName: , qName: user, atts: {code=usercode002, name=username002}
endElement, uri: , localName: , qName: user
characters: startElement, uri: , localName: , qName: extension, atts: {}
characters: startElement, uri: , localName: , qName: property-name, atts: {}
characters: director
endElement, uri: , localName: , qName: property-name
characters: startElement, uri: , localName: , qName: property-value, atts: {}
characters: joke
endElement, uri: , localName: , qName: property-value
characters: endElement, uri: , localName: , qName: extension
characters: endElement, uri: , localName: , qName: department
endDocument

digester是怎么实现的?

digester是SAX的高层次封装,类继承图如下

可以看到其本身就是一个ContentHandler,因此可以在xml解析的各时期收到回调事件,主要是接收startDocument、endDocument、startElement、endElement、characters这五个事件。这种级别上的抽象对于SAX来讲足够好了,但对于使用者来讲不够便利,主要有两点

  • SAX是面向事件的,解析每个标签都会发出startElement、endElement、characters,怎么对不同标签进行不同的处理?
  • 不同标签的处理往往需要创建不同的对象,然后设置不同属性,但创建对象、设置属性本身是通用的,怎么复用?
  • 一个标签会包含属性、子标签、body等,处理子标签时当前标签的上下文怎么处理?

怎么从面向事件转为面向标签?


digester将对标签的不同操作抽象为rule,通过rules维护不同标签的处理操作。当digester解析到某个标签时,就能够获取到该标签的对应的rule,从而调用rules的方法处理当前标签

Rule是怎么抽象的?

Rule表示针对某个标签的具体的操作,digester给了一些默认的Rule

  • ObjectCreateRule:当遇到标签开始时,会创建一个对象,push到对象栈里面;这个标签结束时会从栈里面弹出对象
  • SetPropertiesRule:遇到标签开始时,会从标签中获取属性,填充到对象里
  • SetNextRule:当遇到标签结束时,会将栈顶元素作为参数,调用栈顶第二个元素的指定方法

Rule有四个回调方法:

  • begin。开始解析一个标签时的回调,能够获取到这个标签的属性

  • body。标签结束时的回调,能够获取到这个标签里定义的body

  • end。标签解析结束的回调

  • finish。xml解析结束的回调
    SAX回调什么时候会回调这几个方法呢?

  • startElement事件:begin方法会被调用,传入标签的attribute

  • characters事件:digester只暂存body,不会调用rule方法

  • endElement事件:首先调用body方法,传入标签body;然后调用end方法,没有参数

  • endDocument事件:调用finish方法,rule可以做清理操作

上下文处理

digester的xml处理上下文是基于栈实现的。当处理标签/a时,在开始时会为/a标签创建上下文,当遇到子标签/a/b时,会将/a的上下文push到栈里面,再为/a/b创建上下文,当/a/b标签解析完之后,在从栈里面弹出上下文,继续处理/a标签。具体包括四种栈:

  • 对象栈。标签开始时可以创建对象push到栈里面,标签结束后要弹出
  • rule栈。标签开始时,会从rules里面找到当前标签对应的rule,push到栈里面暂存
  • body栈。暂存每个标签的body内容
  • 参数栈。暂存CallMethodRule的参数

具体就是Digester的几个属性

// 当前body
protected StringBuilder bodyText = new StringBuilder();
// body栈
protected ArrayStack<StringBuilder> bodyTexts = new ArrayStack<>();// rule栈
protected ArrayStack<List<Rule>> matches = new ArrayStack<>(10);// 参数栈
protected ArrayStack<Object> params = new ArrayStack<>();// 暂存的根对象
protected Object root = null;
// 对象栈
protected ArrayStack<Object> stack = new ArrayStack<>();

代码实现

startElement方法(删除了无关代码)

    public void startElement(String namespaceURI, String localName, String qName, Attributes list)throws SAXException {// 对Attribute做一下处理,替换一下占位符list = updateAttributes(list);// 一个新的标签的开始,需要暂存上一个标签的bodyText,并创建新的bodyText,用于接收当前标签的body内容bodyTexts.push(bodyText);bodyText = new StringBuilder();// 获取当前标签名字String name = localName;if ((name == null) || (name.length() < 1)) {name = qName;}// match记录的是当前的标签的处理路径,例如/dept/a。当新标签开始,就需要追加当前标签名字。// 相应的,当标签解析结束时,就需要回退。这个逻辑在endElement方法里StringBuilder sb = new StringBuilder(match);if (match.length() > 0) {sb.append('/');}sb.append(name);match = sb.toString();// 通过Rules获取处理当前标签的Rule,暂存到matches里,并调用每个rule的begin方法List<Rule> rules = getRules().match(namespaceURI, match);matches.push(rules);if ((rules != null) && (rules.size() > 0)) {for (Rule value : rules) {try {Rule rule = value;rule.begin(namespaceURI, name, list);} catch (Exception e) {}}}}

characters方法(删除了无关代码)

    public void characters(char buffer[], int start, int length) throws SAXException {// 就是接收body内容,暂存起来bodyText.append(buffer, start, length);}

endElement方法(删除了无关代码)

    public void endElement(String namespaceURI, String localName, String qName)throws SAXException {// 解析body里面的占位符bodyText = updateBodyText(bodyText);// 提取当前标签名字String name = localName;if ((name == null) || (name.length() < 1)) {name = qName;}// 从rule栈里拿出匹配上的rule。调用rule的body方法List<Rule> rules = matches.pop();if ((rules != null) && (rules.size() > 0)) {String bodyText = this.bodyText.toString().intern();for (Rule value : rules) {try {Rule rule = value;rule.body(namespaceURI, name, bodyText);} catch (Exception e) {}}}// 标签结束,要将外层标签的bodyText弹出,恢复上下文bodyText = bodyTexts.pop();// 调用rule的end方法if (rules != null) {for (int i = 0; i < rules.size(); i++) {int j = (rules.size() - i) - 1;try {Rule rule = rules.get(j);rule.end(namespaceURI, name);} catch (Exception e) {}}}// 恢复match内容int slash = match.lastIndexOf('/');if (slash >= 0) {match = match.substring(0, slash);} else {match = "";}}

endDocument方法(删除了无关代码)

   public void endDocument() throws SAXException {// 清空栈while (getCount() > 1) {pop();}// 调用所有rule的finish方法for (Rule rule : getRules().rules()) {try {rule.finish();} catch (Exception e) {}}clear();}

这里面的对象栈跟参数栈digester并没有修改,而是由Rule去操作这些栈。对象栈由ObjectCreateRule操作、参数栈由CallMethodRule操作

More Details

ObjectCreateRule的实现

public class ObjectCreateRule extends Rule {public ObjectCreateRule(String className) {this(className, (String) null);}public ObjectCreateRule(String className,String attributeName) {this.className = className;this.attributeName = attributeName;}// className就是传进来创建对象的类。attributeName就是获取className的属性名,可以覆盖classNameprotected String attributeName = null;protected String className = null;@Overridepublic void begin(String namespace, String name, Attributes attributes)throws Exception {// 获取className,attributeName中获取的可以覆盖classNameString realClassName = className;if (attributeName != null) {String value = attributes.getValue(attributeName);if (value != null) {realClassName = value;}}// 实例化对象,push到对象栈里面Class<?> clazz = digester.getClassLoader().loadClass(realClassName);Object instance = clazz.getConstructor().newInstance();digester.push(instance);}@Overridepublic void end(String namespace, String name) throws Exception {// end调用时需要弹出当前对象Object top = digester.pop();}
}

SetNextRule的实现。SetNextRule解决每个标签单独创建对象但是不能相互关联的问题,就是将栈顶对象作为参数,通过调用栈里面第二个对象个指定方法进行关联的

public class SetNextRule extends Rule {public SetNextRule(String methodName,String paramType) {this.methodName = methodName;this.paramType = paramType;}// 指定的方法名跟参数类型protected String methodName = null;protected String paramType = null;protected boolean useExactMatch = false;// getter and setter@Overridepublic void end(String namespace, String name) throws Exception {// 取出栈顶元素和第二个元素,调用相应方法Object child = digester.peek(0);Object parent = digester.peek(1);IntrospectionUtils.callMethod1(parent, methodName,child, paramType, digester.getClassLoader());}
}

SetPropertiesRule用来设置栈顶对象的属性

public class SetPropertiesRule extends Rule {@Overridepublic void begin(String namespace, String theName, Attributes attributes)throws Exception {// 获取栈顶对象Object top = digester.peek();// 遍历attribute,通过IntrospectionUtils.setProperty设置属性for (int i = 0; i < attributes.getLength(); i++) {String name = attributes.getLocalName(i);if (name.isEmpty()) {name = attributes.getQName(i);}String value = attributes.getValue(i);if (!digester.isFakeAttribute(top, name)&& !IntrospectionUtils.setProperty(top, name, value)&& digester.getRulesValidation()) {}}}
}

怎么持有根节点?

ObjectCreateRule在标签开始时创建对象,在标签结束时弹出对象,那么当xml解析完毕,根对象也就弹出去了,怎么持有根对象呢?有两种方式
第一种方式,digester的push方法会判断栈是否为空,空的话会暂存到root属性上,解析结束后可以通过root属性获取根对象

  public void push(Object object) {if (stack.size() == 0) {root = object;}stack.push(object);}

第二种方式,digester使用方push一个对象到栈里面,当作跟对象的“容器”,解析根对象的时候SetNextRule将根对象关联到“容器”里

catalina中digester的使用

catalina里通过digester解析server.xml(有删除)

    protected Digester createStartDigester() {Digester digester = new Digester();// 1、创建Server,2、设置属性,3、将server关联到外部catalina实例(createStartDigester之后还会将Catalina实例push进去)digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");digester.addSetProperties("Server");digester.addSetNext("Server","setServer","org.apache.catalina.Server");// 创建listener、设置属性、关联到Serverdigester.addObjectCreate("Server/Listener",null, // MUST be specified in the element"className");digester.addSetProperties("Server/Listener");digester.addSetNext("Server/Listener","addLifecycleListener","org.apache.catalina.LifecycleListener");// 创建service、设置属性、关联到Serverdigester.addObjectCreate("Server/Service","org.apache.catalina.core.StandardService","className");digester.addSetProperties("Server/Service");digester.addSetNext("Server/Service","addService","org.apache.catalina.Service");// 为service创建listener,关联到servicedigester.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,关联到servicedigester.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");// 创建Connector,关联到Servicedigester.addRule("Server/Service/Connector",new ConnectorCreateRule());digester.addRule("Server/Service/Connector",new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));digester.addSetNext("Server/Service/Connector","addConnector","org.apache.catalina.connector.Connector");// 创建Listener,关联到Connectordigester.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");// 为Engine、Host、Context创建Ruledigester.addRuleSet(new EngineRuleSet("Server/Service/"));digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));return digester;}

tomcat xml解析-digester相关推荐

  1. Spring的XML解析原理,java软件开发面试常见问题

    前言 很多同学想进大厂,特别是刚毕业的,以及工作年限短的,不要有任何侥幸心理,踏踏实实的把基础弄扎实了,这是你通往高薪之路的唯一正确姿势. 首先从面试题做起~好了,不多说了,直接上正菜. 在这里分享一 ...

  2. java xml开源操作类,xml解析和操作的开源工具项目涵盖java c++ php 等语言

    XML解析器-Xerces    XML操作库-dom4j    XML文档解析器-Digester    J2ME-的XML-解析器-kXML XML解析类库-MXP1    XML解析器-LibX ...

  3. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  4. XML 解析技术:DOM4j

    前言: 我一直以来就坚信一句话:不积跬步无以至千里,不积小流无以成江海.可能很多人对一些基础概念会选择性忽略,但往往基础和知识理解深度决定你的上限. 今天给大家带来一个对框架以及容器源码理解提升的知识 ...

  5. c语言解析xml字符串_Python XML解析

    Python XML解析 什么是XML? XML 指可扩展标记语言(eXtensible Markup Language). 你可以通过本站学习XML教程 XML 被设计用来传输和存储数据. XML是 ...

  6. XML解析简介及Xerces-C++简单使用举例

    XML是由World WideWeb联盟(W3C)定义的元语言.它已经成为一种通用的数据交换格式,它的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的方便.XML在不同的语言里解析方式 ...

  7. Java XML解析工具 dom4j介绍及使用实例

    Java XML解析工具 dom4j介绍及使用实例 dom4j介绍 dom4j的项目地址:http://sourceforge.net/projects/dom4j/?source=directory ...

  8. C++ XML解析之TinyXML篇[转]

    最 近使用TinyXML进行C++ XML解析,感觉使用起来比较简单,很容易上手,本文给出一个使用TinyXML进行XML解析的简单例子,很多复杂的应用都可以基于本例子的方法来完 成.以后的文章里会讲 ...

  9. Java XML解析器

    使用Apache Xerces解析XML文档 一.技术概述 在用Java解析XML时候,一般都使用现成XML解析器来完成,自己编码解析是一件很棘手的问题,对程序员要求很高,一般也没有专业厂商或者开源组 ...

  10. XML解析方式(来自 传智播客 方立勋视频教程)

    为什么80%的码农都做不了架构师?>>>    XML解析方式一般有两种:DOM和SAX DOM:(Document Object Model,即文档对象模型)是W3C组织推荐的解析 ...

最新文章

  1. Windows 7任务栏图标特别说明
  2. 解决INVALID BOUND STATEMENT (NOT FOUND)(MYBATIS的MAPPER绑定问题)
  3. uboot nand erase 的显示错误修复
  4. 【深度学习的数学】2×3×1层带sigmoid激活函数的神经网络感知机对三角形平面的分类训练预测(绘制出模型结果三维图展示效果)(梯度下降法+最小二乘法+激活函数sigmoid+误差反向传播法)
  5. 【机器学习基础】数学推导+纯Python实现机器学习算法18:奇异值分解SVD
  6. python运算符讲解_python运算符讲解
  7. 梦想中的网络安全和内部协作
  8. mysql 恢复root用户_mysql误删root用户恢复方案
  9. javascript 变量作用域
  10. 参考文献自动搜集管理完美攻略(图文版): Latex+Lyx+Zotero
  11. Bootstrap 表单的动作按钮
  12. Javascript中操作cookie
  13. 这些将在新一年改变你的风控内容
  14. python经典程序实例-你不知道的Python语言的经典五大案例
  15. 吴恩达神经网络和深度学习-学习笔记-25-定位数据不匹配
  16. 人物关系图谱:ECharts 实现
  17. java 集合工具类_Java 集合 Collections工具类
  18. 追忆我的2008-养成做笔记的习惯
  19. 校园门禁app开发的功能
  20. 气象数据的简单数据分析处理——基于Notebook

热门文章

  1. 新手入门:盘点Web测试与APP测试的异同点
  2. ddr3ddr4 lpddr4速率_LPDDR4和LPDDR3性能差别多少 LPDDR4和LPDDR3参数对比
  3. 使用WireShark生成地理位置数据地图
  4. 【毕业季】一个普通大二学生的迷茫与展望
  5. excel财务案例建模_Pro Excel财务建模:技术创业公司的构建模型
  6. android gps信号检测工具,【分享】GPS Test Plus全球GPS定位卫星信号检测工具
  7. 2021-07-28 cad贱人工具箱5.8
  8. Android app客户端性能测试工具Emmagee 浅析
  9. Java如何在创建文件时指定编码
  10. 【JavaWeb学习】14综合案例