简介
Digester是Apache Commons包中的一个子项目,是一个将XML文件转换成Java对象的通用实现。底层采用SAX,以事件驱动方式匹配元素,然后调用预定义的规则将XML文档转换为对象。
Digester屏蔽了SAX解析的细节,使用者只需关注元素匹配模式和触发规则就可以完成XML->Java对象转换操作,大大简化了XML解析工作。
阅读Tomcat源码时发现server.xml解析使用到了Digester。server.xml解析完成后直接就可以使用StandardServer等组件,所以还是需要了解下Digester的用法。
引入依赖
Digester3.2至少需要JDK1.5,具体信息可以查看官网。官网地址
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
</dependency>
匹配模式和规则
Digester核心是匹配模式+规则,模式即是XML文档中元素节点,规则即是匹配到后需要执行的操作。
1.元素匹配模式
使用一个简单的字符串(如a)匹配XML文档中的顶级元素,但是不会匹配嵌套元素。
使用一个有层次结构的字符串(如a/b)匹配XML文档中的层次嵌套结构,可以使用多个斜线来定义任何所需深度的层次结构。
使用“*”通配符匹配可匹配XML文档中特定的元素(嵌套的也可以),如 */a。
<a> -- Matches pattern "a"
<b> -- Matches pattern "a/b"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
</b>
<b> -- Matches pattern "a/b"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
</b>
</a>
注:如果可以找到显式匹配,那么会忽略通配符模式。如*/a 和 x/a, */a可以为所有这种层次嵌套结构添加规则,但是随后针对x/a这种显式匹配会覆盖通配符添加的规则。
Rule ruleA = new ObjectCreateRule();
Rule ruleB = new SetPropertiesRule();
Rule ruleC = new SetNextRule();
digester.addRule("*/a", ruleA);
digester.addRule("*/a", ruleB);
digester.addRule("x/a", ruleA); //显式匹配覆盖通配符添加的规则,x/a需要ruleA、ruleB、ruleC三种规则就需要单独添加。
digester.addRule("x/a", ruleB);
digester.addRule("x/a", ruleC);
2.规则:定义模式匹配时需要执行的操作。
每个规则需要实现以下一个或多个事件方法,当与该规则对应的匹配模式触发时,这些方法在明确的时间被调用:
- begin() - 在遇到匹配的 XML 元素的开头时调用。包含与此元素对应的所有属性的数据结构也被传递。
- body() - 在遇到匹配元素的嵌套内容(本身不是 XML 元素)时调用。作为解析过程的一部分,任何前导或尾随空格都将被删除。
- end() - 在遇到匹配的 XML 元素的结尾时调用。如果匹配其他处理规则的嵌套 XML 元素包含在此元素的主体中,则在调用此方法之前,已完成匹配规则的相应处理规则。
- finish() - 在解析完成时调用,让每个规则有机会清理它们可能创建和缓存的任何临时数据。
Digester提供了一组处理规则实现类,可以处理很多常见的编程场景。
- ObjectCreateRule:当begin()方法被调用时,会实例化指定Java类的实例(默认使用无参构造),并将其推送到栈上。当end()方法被调用时,栈顶的对象会被弹出。
- FactoryCreateRule:同ObjectCreateRule,对象的实例由使用者创建,这样可以在将对象推送到栈中之前执行其他设置处理。
- SetPropertiesRule:当begin()方法被调用时,digester使用反射给栈顶对象相应的属性赋值。
- SetNextRule:当end()方法被调用时,digester从栈中取出顶部的下一个元素,然后调用该对象相应的方法将栈顶部的对象作为参数传递。此规则通常用于在两个对象之间建立一对多的关系,方法名称通常类似于“addChild”。
- SetTopRule:当end()方法被调用时,digester从栈中取出栈顶元素,然后调用该对象相应的方法将栈顶上的下一个对象作为参数传递。此规则将用作SetNextRule的替代方法,具有典型的方法名称“setParent”。
- CallMethodRule:当end()方法被调用时,此规则设置对栈上顶部对象的方法调用。参数的值通过后继的CallParamRule给出。
- CallParamRule:此规则设置嵌套在CallMethodRule的特定编号(相对于零)参数的来源。
对于绝大多数情况,Digester默认提供的处理规则就可以正常工作了。但如果需要自定义处理规则,可以继承Rules接口或者RulesBase基类自行实现相应的方法。
注:元素添加了多个处理规则时会按照添加顺序依次执行,规则方法begin按照添加的顺序执行,end按照相反的顺序执行。所以以正确的顺序配置规则是成功使用Digester处理XML文件的关键,
一般按照XML文档的元素结构从上到下配置应该就没有问题。
Digester其他配置属性可以查看官网,如classLoader、errorHandler等。注意:默认情况下,遇到的任何解析错误都会被记录下来,Digester会继续执行。
源码分析
Digester底层采用SAX,SAX的特点是事件驱动,自顶而下边扫描边解析。DefaultHandler是SAX2事件处理程序的默认基类,Digester继承了DefaultHandler并作为contentHandler传入SAX解析器中。 SAX在解析XML文档时触发事件,Digester会执行相应的方法。如文档开始解析时会触发通知执行startDocument方法,解析标签节点是会触发通知执行startElement方法。
public class Digester extends DefaultHandler {
// 解析方法,有其他不同参数的重载方法
public <T> T parse( InputStream input ) throws IOException, SAXException {
if ( input == null ) {
throw new IllegalArgumentException( "InputStream to parse is null" );
}
return ( this.<T> parse( new InputSource( input ) ) );
}
public <T> T parse( InputSource input ) throws IOException, SAXException {
if ( input == null ) {
throw new IllegalArgumentException( "InputSource to parse is null" );
}
// 提供一个钩子,默认实现什么都不做,但子类可以根据需要重写。
configure();
String systemId = input.getSystemId();
if ( systemId == null ) {
systemId = "(already loaded from stream)";
}
try {
// 获取解析器,开始解析文档
getXMLReader().parse( input );
} catch ( IOException e ) {
throw e;
} catch ( SAXException e ) {
throw e;
}
cleanup();
return this.<T> getRoot();
}
public XMLReader getXMLReader() throws SAXException {
if ( reader == null ) {
reader = getParser().getXMLReader();
}
reader.setDTDHandler( this );
// 设置contentHandler, 就是digester实例
reader.setContentHandler( this );
if ( entityResolver == null ) {
reader.setEntityResolver( this );
} else {
reader.setEntityResolver( entityResolver );
}
reader.setErrorHandler( this );
return reader;
}
public SAXParser getParser() {
if ( parser != null ) {
return ( parser );
}
try {
// 创建SAX解析器
parser = getFactory().newSAXParser();
} catch ( Exception e ) {
return ( null );
}
return ( parser );
}
public SAXParserFactory getFactory() {
if ( factory == null ) {
// 创建SAX解析工厂
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware( namespaceAware );
factory.setXIncludeAware( xincludeAware );
factory.setValidating( validating );
factory.setSchema( schema );
}
return ( factory );
}
@Override
public void startDocument() throws SAXException {
// 提供一个钩子,默认实现什么都不做,但子类可以根据需要重写。
configure();
}
@Override
public void startElement( String namespaceURI, String localName, String qName, Attributes list ) throws SAXException {
// namespaceURI:xml文档的命名空间, localName:标签的名字,qName:带命名空间的标签的名字,list:标签的属性集合
// 自定义处理器,默认为null
if ( customContentHandler != null ) {
customContentHandler.startElement( namespaceURI, localName, qName, list );
return;
}
// 保存当前元素的文本内容到栈中
bodyTexts.push( bodyText );
bodyText = new StringBuilder();
// 实际的元素名可以是localName,也可以是qName,这取决于解析器是否支持名称空间
String name = localName;
if ( ( name == null ) || ( name.length() < 1 ) ) {
name = qName;
}
// 匹配模式
StringBuilder sb = new StringBuilder( match );
if ( match.length() > 0 ) {
sb.append( '/' );
}
sb.append( name );
match = sb.toString();
// 获取匹配规则
List<Rule> rules = getRules().match( namespaceURI, match, localName, list );
// 将匹配规则保存到栈中
matches.push( rules );
if ( ( rules != null ) && ( rules.size() > 0 ) ) {
Substitutor substitutor = getSubstitutor();
if ( substitutor != null ) {
list = substitutor.substitute( list );
}
for ( int i = 0; i < rules.size(); i++ ) {
try {
Rule rule = rules.get( i );
// 执行规则的begin方法
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;
}
}
}
}
@Override
public void endElement( String namespaceURI, String localName, String qName ) throws SAXException {
// 自定义处理器,默认为null
if ( customContentHandler != null ) {
customContentHandler.endElement( namespaceURI, localName, qName );
return;
}
// 实际的元素名可以是localName,也可以是qName,这取决于解析器是否支持名称空间
String name = localName;
if ( ( name == null ) || ( name.length() < 1 ) ) {
name = qName;
}
// 从栈取出对应的规则
List<Rule> rules = matches.pop();
if ( ( rules != null ) && ( rules.size() > 0 ) ) {
String bodyText = this.bodyText.toString();
Substitutor substitutor = getSubstitutor();
if ( substitutor != null ) {
bodyText = substitutor.substitute( bodyText );
}
for ( int i = 0; i < rules.size(); i++ ) {
try {
Rule rule = rules.get( i );
// 处理规则的body方法
rule.body( namespaceURI, name, bodyText );
} catch ( Exception e ) {
log.error( "Body event threw exception", e );
throw createSAXException( e );
} catch ( Error e ) {
log.error( "Body event threw error", e );
throw e;
}
}
}
// 从栈中取出当前元素的文本
bodyText = bodyTexts.pop();
// 以相反的顺序执行规则集合中end方法
if ( rules != null ) {
for ( int i = 0; i < rules.size(); i++ ) {
int j = ( rules.size() - i ) - 1;
try {
Rule rule = rules.get( j );
// 执行规则的end方法
rule.end( namespaceURI, name );
} catch ( Exception e ) {
log.error( "End event threw exception", e );
throw createSAXException( e );
} catch ( Error e ) {
log.error( "End event threw error", e );
throw e;
}
}
}
// 恢复上一个匹配表达式
int slash = match.lastIndexOf( '/' );
if ( slash >= 0 ) {
match = match.substring( 0, slash );
} else {
match = "";
}
}
@Override
public void endDocument() throws SAXException {
// 获取所有定义的规则并执行规则的finish方法
for ( Rule rule : getRules().rules() ) {
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;
}
}
// 执行最终清理
clear();
}
}
Digester规则抽象类,这里只详述ObjectCreateRule规则,其他规则类似,都是继承Rule实现相应的方法。
public abstract class Rule {
public void begin( String namespace, String name, Attributes attributes ) throws Exception{
// The default implementation does nothing
}
public void body( String namespace, String name, String text ) throws Exception{
// The default implementation does nothing
}
public void end( String namespace, String name ) throws Exception{
// The default implementation does nothing
}
public void finish() throws Exception {
// The default implementation does nothing
}
}
// 创建对象规则
public class ObjectCreateRule extends Rule {
@Override
public void begin( String namespace, String name, Attributes attributes ) throws Exception {
Class<?> clazz = this.clazz;
if ( clazz == null ) {
// 实例化类的名称
String realClassName = className;
if ( attributeName != null ) {
String value = attributes.getValue( attributeName );
if ( value != null ) {
realClassName = value;
}
}
// 加载类
clazz = getDigester().getClassLoader().loadClass( realClassName );
}
Object instance;
// 没有设置构造参数,默认使用无参构造实例化对象
if ( constructorArgumentTypes == null || constructorArgumentTypes.length == 0 ) {
instance = clazz.newInstance();
} else {
if ( proxyManager == null ) {
// 获取有参构造
Constructor<?> constructor = getAccessibleConstructor( clazz, constructorArgumentTypes );
if ( constructor == null ) {
throw new SAXException();
}
// 使用cglib动态代理创建对象
proxyManager = new ProxyManager( clazz, constructor, defaultConstructorArguments, getDigester() );
}
instance = proxyManager.createProxy();
}
// 将实例化对象保存到栈中
getDigester().push( instance );
}
@Override
public void end( String namespace, String name ) throws Exception {
// 从栈中取出实例对象
Object top = getDigester().pop();
// 销毁对象
if ( proxyManager != null ) {
proxyManager.finalize( top );
}
}
}
使用示例
xml文件
<school name="希望小学">
<grade level="1">
<student name="张三" age="6"/>
<student name="李四" age="6"/>
</grade>
<grade level="2">
<student name="王五" age="8"/>
</grade>
</school>
转换对象
@Data
@ObjectCreate(pattern = "school")
public class School {
@SetProperty(pattern = "school")
private String name;
private List<Grade> grades = new ArrayList<>();
@SetNext
public void addGrade(Grade grade) {
this.grades.add(grade);
}
}
@Data
@ObjectCreate(pattern = "school/grade")
public class Grade {
@SetProperty(pattern = "school/grade")
private String level;
private List<Student> students = new ArrayList<Student>();
@SetNext
public void addStudent(Student student) {
this.students.add(student);
}
public Student findStudent(String name) {
return students.stream().filter(student -> StringUtils.equals(student.getName(), name)).findFirst().orElse(null);
}
}
@Data
@ObjectCreate(pattern = "school/grade/student")
public class Student {
@SetProperty(pattern = "school/grade/student")
private String name;
@SetProperty(pattern = "school/grade/student")
private int age;
}
解析
官网详述了Digester 1.X、Digester 2.X和Digester 3.X的区别,主要是Digester实例的创建方式,建议使用新的方式创建Digester。
规则的注入除了用下面代码所示的手动配置,还有两种方式:a.将规则写到xml文件中(FromXmlRulesModule) b.采用注解的方式(FromAnnotationsRuleModule)
public class Test {
// Digester 3.X的使用方式:“configure once, create everywhere”
private static final DigesterLoader loader = DigesterLoader.newLoader(new SchoolModule());
private static final DigesterLoader loader2 = DigesterLoader.newLoader(new AnnotationModule());
public static void main(String[] args) throws IOException, SAXException {
InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("school.xml");
// Digester 1.X的使用方式:“given a Digester instance, then configure it”
Digester digester = new Digester();
digester.addObjectCreate("school", School.class);
digester.addSetProperties("school");
digester.addObjectCreate("school/grade", Grade.class);
digester.addSetProperties("school/grade");
digester.addSetNext("school/grade", "addGrade");
digester.addObjectCreate("school/grade/student", Student.class);
digester.addSetProperties("school/grade/student");
digester.addSetNext("school/grade/student", "addStudent");
School school = digester.parse(inputStream);
System.out.println(JSON.toJSONString(school));
// Digester 3.X
Digester digester2 = loader.newDigester();
School school2 = digester2.parse(inputStream);
System.out.println(JSON.toJSONString(school2));
// 使用注解的方式
Digester digester3 = loader2.newDigester();
School school3 = digester3.parse(inputStream);
System.out.println(JSON.toJSONString(school3));
}
// 也可以实现RulesModule接口
static class SchoolModule extends AbstractRulesModule {
@Override
protected void configure() {
forPattern("school").createObject().ofType(School.class);
forPattern("school").setProperties();
forPattern("school/grade").createObject().ofType(Grade.class);
forPattern("school/grade").setProperties();
forPattern("school/grade").setNext("addGrade");
forPattern("school/grade/student").createObject().ofType(Student.class);
forPattern("school/grade/student").setProperties();
forPattern("school/grade/student").setNext("addStudent");
}
}
// 注解方式,xml方式注入的在这里就不详述了
static class AnnotationModule extends FromAnnotationsRuleModule {
@Override
protected void configureRules() {
bindRulesFrom(School.class);
}
}
}