Apache Commons Digester 使用介绍

953 阅读9分钟

简介

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

digester_1.png