Digester原理解析
前面我们介绍过如何 手写一个XML解析器。实际上我们就是按照Digester
的基本原理来实现的。下面我们根据Digester
的源码再来回顾一遍。
源码结构
DefaultHandler
:由SAX
提供,Digester
继承自该类。Digester
:事件处理器。组件核心类。基于SAX
封装了一系列对使用者友好的API
。Rules
:接口,抽象了对解析规则的添加、删除和匹配等方法。AbstractRulesImpl
:Rules
接口的抽象实现,实际没什么实现,主要用来维护一些公共属性。RulesBase
:Digester
使用的Rules
接口的默认实现类。实际是一个工厂,内部用一个HashMap
来维护XML
节点和解析规则的映射关系。Rule
:解析规则接口。抽象了解析XML
的过程(生命周期)。ObjectCreateRule
:Rule
接口的实现类。提供创建对象功能。SetPropertiesRule
:Rule
接口的实现类。提供对象属性赋值功能。SetNextRule
:Rule
接口的实现类。提供对象方法调用功能。
回顾一下我们自己手写的XML
解析器。Rule
规则接口相当于我们的Processor
策略接口,RulesBase
相当于我们的ProcessorFactory
策略工厂。整体的架构体现了策略+工厂模式。
使用示例
回顾一下我们的使用方法:
1 | // 创建Digester对象 |
其中createDigester()
方法中指定了一系列的解析规则。digester.push(this);
会将当前对象压入对象栈顶,对象栈的底层数据结构是java.util.Stack
,Digester
封装了一些空指针安全的操作对象栈的方法以供使用。主要有以下方法:
push(T object)
:压入一个元素至栈顶。pop()
:弹出栈顶元素。peek()
:获取栈顶元素的引用(元素不会出栈)。peek(int n)
:获取栈中从栈顶到栈底索引位置为n
的元素的引用(元素不会出栈)。
解析规则:XML
节点的生命周期
Digester
用Rule
类抽象了解析整个XML
文件节点的生命周期,包含以下几个阶段:
begin
:解析到匹配节点的开始标签部分。body
:解析到匹配节点的普通文本内容。end
:解析到匹配节点的结束标签部分。此处end
方法的整体调用顺序应该是先begin
的标签后end
。finish
:解析完所有的节点。
Rule
类是一个抽象类,这些生命周期方法都是空方法,需要由子类进行具体实现。
解析规则实现
Digester
包中提供的一些常用解析规则实现。
CreateObjectRule
:begin
方法会将指定的类实例化并压入对象栈顶。具体的类可在该规则类的构造方法中指定;也可通过当前解析节点的某个属性指定,属性名称通过构造方法传入。end
方法会将创建的对象从对象栈中弹出。SetPropertiesRule
:begin
方法会使用标准Java Bean
属性操作方法(setter
方法)将当前解析节点的属性值设置到对象栈的栈顶元素中。SetNextRule
:end
方法会将栈顶元素作为入参,调用栈顶元素的下一个元素的指定方法。一般用于建立对象的“父子”关系,形成对象引用树。CallMethodRule
:end
方法会调用对象栈的栈顶元素的指定方法。
这些解析规则的实现原理和前面 手写一个XML解析器 中的一样,都是通过反射来实现的。
自定义规则
当Digester
包提供的Rule
解析规则无法满足需求时,可进行规则的自定义。只需要继承Rule
抽象类或其相关子类,并重写需要的生命周期方法即可。
例如:
1 | public class ListenerCreateRule extends ObjectCreateRule { |
1 | public class ConnectorCreateRule extends Rule { |
addRuleSet
针对相同节点,但是其父节点不同的情况,我们的解析规则定义会出现重复。这时我们可以将重复的解析规则定义成一个RuleSet
进行复用。
RuleSet
接口定义如下:
1 | public interface RuleSet { |
实现类需要有一个prefix
前缀来匹配不同的父节点,例如:
1 | public class NamingRuleSet implements RuleSet { |
在使用时通过传入不同的前缀来匹配父节点不同的节点,例如:
1 | digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); |
上面的规则会分别匹配到Server/GlobalNamingResources/
下的Ejb
节点和Server/Service/Engine/Host/Context/
下的Ejb
节点。这样我们就复用了解析规则的定义。