[Tomcat源码相关]Digester类的使用

apache的Digester库是专门用解析管理xml文档,在tomcat中也使用了这个第三方类库来解析xml文档,也就是对应的server.xmlweb.xml,所以我们今天先讲解下Digester类库的基本使用方法。

org.apache.commons.digester.Digester类是Digester库的主类,该类可以用来解析xml文档,对于xml文档中的每个元素,Digester对象都会检查他事先预定义的事件。在调用Digester对象parse()方法之前,需要预先定义事件来完成解析。

名词

<?xml version="1.0" encoding="gb2312"?>
<leader name="leader" age="30" className="learn.tomcat.digester.model.Leader">
    <crew money="3000" name="zuyuan">
        <gender value="男" />
    </crew>
</leader>
  • 模式:对于上述xml文档,根元素是leader,那么leader元素的模式就是leader,对于leader的子元素crew,模式是leader/crew,模式用来标识唯一元素。模式的规则:父元素名+"/"+子元素名
  • 规则:规则是指当Digester对象开始解析某个元素的时候需要执行的一个或者多个动作。规则的实例是org.apache.commons.digester.Rule。Digester可以包含0个或者多个规则。当Digester类匹配到对应模式的元素的开始标签的时候,会调用对应Rule对像的begin方法,当解析到标签内部的时候会触发body方法,当解析标签结束的时候会触发end方法。

下面我们详细讲解下Digester类常用的方法。

1、创建对象

当想让Digester对象遇到某个模式的元素的时候创建对象,可以调用addObjectCreate()方法。

//4个重载方法
public void addObjectCreate(String pattern, String className)
public void addObjectCreate(String pattern, Class clazz)
public void addObjectCreate(String pattern, String className,String attributeName)
public void addObjectCreate(String pattern,String attributeName,Class clazz)

测试代码

package learn.tomcat.digester;

import org.apache.commons.digester.Digester;
import java.io.File;

public class DigesterTest {

private static File file = new File("D:\\workspace\\info\\src\\main\\java\\learn\\tomcat\\digester\\test2.xml");
private static Digester digester = new Digester();

public static void main(String[] args) throws Exception{
    testCreate();
}

/**
 * 测试创建对象
 */
private static void testCreate() throws Exception{
    /*
    简单的创建对象的方法,
    第一个参数是 匹配模式,父标签直接使用标签元素名即可,子标签需要遵守模式的命名规则
    第二个参数可以是class类型也可以是String类型,如果是String类型需要是全包路径
     */
//        digester.addObjectCreate("leader", Leader.class);
//        digester.addObjectCreate("leader", "learn.tomcat.digester.model.Leader");
    /*
    创建对象的重载方法
    第一个参数表示匹配模式
    第二个参数表示遇到该匹配模式默认的实现类
    第三个参数表示元素的属性名称
    该方法遵循一下运作方式,当匹配到的元素属性包含第三个参数的属性的时候,使用标签内部属性的值来初始化类
    否则使用第二个参数表示的默认类来初始化
     */
//        digester.addObjectCreate("leader", "learn.tomcat.digester.model.Leader", "className");
//        digester.addObjectCreate("leader", "className", Leader.class);
    Object parse = digester.parse(file);
    System.out.println(parse.toString());
}

}

2、设置属性

设置属性可以为创建出来的对象设置属性,将标签上面的属性设置到对应对象中,可以调用Digester对象的addSetProperties方法。

public void addSetProperties(String pattern) 
public void addSetProperties(String pattern,String attributeName,String propertyName) 
public void addSetProperties(String pattern,String [] attributeNames,String [] propertyNames)
public void addSetProperty(String pattern, String name, String value)

测试代码如下

    private static File file = new File("D:\\workspace\\info\\src\\main\\java\\learn\\tomcat\\digester\\test2.xml");
private static Digester digester = new Digester();

public static void main(String[] args) throws Exception{
    testSetProperties();
}

/**
     * 测试设置属性
     */
private static void testSetProperties() throws Exception {
    digester.addObjectCreate("leader", Leader.class);
    /*
    第一个参数:匹配模式,如果是子标签的话,需要遵循标签匹配模式的规则
    第二个参数:标签上属性的名称
    第三个参数:对应实体中的属性的名称
     */
	//digester.addSetProperties("leader", "name", "name");

    /*
    public void addSetProperties(String pattern,String attributeName,String propertyName)
    的多参数版本
     */
    String[] attributes = {"name", "age"};
    String[] properties = {"name", "age"};
	//digester.addSetProperties("leader",attributes ,properties);
    /*
    简单版本,参数表示匹配模式,默认元素标签上的属性和对象内部的属性名称相同,如果不相同则无法设置
     */
    digester.addSetProperties("leader");
    Object parse = digester.parse(file);
    System.out.println(parse);
}

3、调用方法
Digester类允许通过添加一条规则,使Digester在遇到与该规则相关联的匹配模式的时候调用内部栈最顶端的对象的某个方法。使用addCallMethod完成该功能。

public void addCallMethod(String pattern, String methodName)

测试代码如下:

public class DigesterTest {

private static File file = new File("D:\\workspace\\info\\src\\main\\java\\learn\\tomcat\\digester\\test2.xml");
private static Digester digester = new Digester();

public static void main(String[] args) throws Exception{
    testCallMethod();
}

/**
 * 测试方法调用
 */
private static void testCallMethod() throws Exception{
    digester.addObjectCreate("leader", Leader.class);
    digester.addSetProperties("leader");
    /*
    第一个参数:匹配模式
    第二个参数:需要调用的方法名称
    注意:需要调用的方法必须为public
     */
    digester.addCallMethod("leader", "sayName");
    Object parse = digester.parse(file);
    System.out.println(parse);
}

4、创建对象之间的关系

Digester内部有个对象栈,用于临时存储创建的对象,当使用addObjectCreate方法实例化一个对象的时候,创建成功的对象就会被push到栈中。当调用2次addObjectCreate方法以后栈中就存在两个对象,这时候再调用addSetNext方法,那么就会把第二个对象作为参数传递给第一个对象指定的方法。

public void addSetNext(String pattern, String methodName)
public void addSetNext(String pattern, String methodName,String paramType)

测试代码如下:

private static File file = new File("D:\\workspace\\info\\src\\main\\java\\learn\\tomcat\\digester\\test2.xml");
private static Digester digester = new Digester();

public static void main(String[] args) throws Exception{
    testAddSetNext();
}

/**
 * 测试 对象关系设置
 */
private static void testAddSetNext() throws Exception{
    digester.addObjectCreate("leader", Leader.class);
    digester.addSetProperties("leader");
    digester.addObjectCreate("leader/crew", Crew.class);
    digester.addSetProperties("leader/crew");
    /*
     * 参数一:匹配模式
     * 参数二:将第二个对象传递个第一个对象时调用的第一个对象的方法
     */
    digester.addSetNext("leader/crew", "setCrew");
    Object parse = digester.parse(file);
    System.out.println(parse);
}

Rule类

Rule类也是Digester中比较重要的类,包含了一些常用的方法,比如begin(),body(),end()等,当Digester对象解析到标签开始的时候会调用begin()方法,到标签内会调用body()方法,在标签末位的时候会调用end方法。我们可以查看下addObjectCreate方法看看,该方法如何实现。

    /**
 * Add an "object create" rule for the specified parameters.
 *
 * @param pattern Element matching pattern
 * @param clazz Java class to be created
 * @see ObjectCreateRule
 */
public void addObjectCreate(String pattern, Class clazz) {

    addRule(pattern,
            new ObjectCreateRule(clazz));

}

其他重载的方法都是类似的。都调用了addRule()这个方法,我们查看下addRule这个方法。

  /**
 * <p>Register a new Rule matching the specified pattern.
 * This method sets the <code>Digester</code> property on the rule.</p>
 *
 * @param pattern Element matching pattern
 * @param rule Rule to be registered
 */
public void addRule(String pattern, Rule rule) {

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

}

其中ObjectCreateRule类是Rule类的子类

public class ObjectCreateRule extends Rule 

我们再继续往下看

protected Rules rules = null;

/**
 * Return the <code>Rules</code> implementation object containing our
 * rules collection and associated matching policy.  If none has been
 * established, a default implementation will be created and returned.
 */
public Rules getRules() {

    if (this.rules == null) {
        this.rules = new RulesBase();
        this.rules.setDigester(this);
    }
    return (this.rules);

}

可以看到addRule是往Digester内部的一个对象rules中添加了一条rule,而其中rules指向的是一个RuleBase对象,我们来查看下RuleBase签名

public class RulesBase implements Rules

可以看到RuleBaseRules的实现类,Rules定义了一些方法,RuleBase做了实现,我们关心add方法可以直接查看add方法。

 protected HashMap cache = new HashMap();
protected ArrayList rules = new ArrayList();

/**
 * Register a new Rule instance matching the specified pattern.
 *
 * @param pattern Nesting pattern to be matched for this Rule
 * @param rule Rule instance to be registered
 */
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);
    }

}

从上面add的代码可以看出来 Digester内部有个RuleBase类,代表这该Digester类所包含的所有的规则集合。RuleBase内部有个HashMap存放着所有的规则,map的key是匹配规则,值是该规则对应的所有Rule的集合。

RuleSet类

如果想向Digester类中再添加规则的时候可以直接调用addRuleSet()方法,我们看下方法签名。

/**
 * Register a set of Rule instances defined in a RuleSet.
 *
 * @param ruleSet The RuleSet instance to configure from
 */
public void addRuleSet(RuleSet ruleSet) {

    String oldNamespaceURI = getRuleNamespaceURI();
    String newNamespaceURI = ruleSet.getNamespaceURI();
    if (log.isDebugEnabled()) {
        if (newNamespaceURI == null) {
            log.debug("addRuleSet() with no namespace URI");
        } else {
            log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
        }
    }
    setRuleNamespaceURI(newNamespaceURI);
    ruleSet.addRuleInstances(this);
    setRuleNamespaceURI(oldNamespaceURI);

}

参数是一个RuleSet的实例。

public interface RuleSet {
/**
 * Return the namespace URI that will be applied to all Rule instances
 * created from this RuleSet.
 */
public String getNamespaceURI();

/**
 * Add the set of Rule instances defined in this RuleSet to the
 * specified <code>Digester</code> instance, associating them with
 * our namespace URI (if any).  This method should only be called
 * by a Digester instance.
 *
 * @param digester Digester instance to which the new Rule instances
 *  should be added.
 */
public void addRuleInstances(Digester digester);

}

RuleSet是一个接口定义了两个方法,他有个抽象实现类,实现了getNamespaceURI(),所以我们可以直接自定义类继承RuleSetBase类,只需要实现addRuleInstances()方法即可。

public abstract class RuleSetBase implements RuleSet {

 protected String namespaceURI = null;

/**
 * Return the namespace URI that will be applied to all Rule instances
 * created from this RuleSet.
 */
public String getNamespaceURI() {

    return (this.namespaceURI);

}

/**
 * Add the set of Rule instances defined in this RuleSet to the
 * specified <code>Digester</code> instance, associating them with
 * our namespace URI (if any).  This method should only be called
 * by a Digester instance.
 *
 * @param digester Digester instance to which the new Rule instances
 *  should be added.
 */
public abstract void addRuleInstances(Digester digester);


}

那么在有了很多添加规则的方法以后,为什么还要使用这个方法呢?主要的原因就是根据自定义xml可以写出很多使用Digester的代码,而每个xml解析写出来的代码是不同的,如果都放在一起显得代码凌乱,所以可以单独抽出来,写到一个类的一个方法里,非常类似你写多线程的run方法的时候,可以写匿名内部类,也可以单独写一个自定义的类来实现Runnable然后在类的run方法里写实现代码,两者并无本质区别。

我们写一些测试代码:

private static File file = new File("D:\\workspace\\info\\src\\main\\java\\learn\\tomcat\\digester\\test2.xml");
private static Digester digester = new Digester();

public static void main(String[] args) throws Exception{
    RuleSet myRule = new MyRule();
    digester.addRuleSet(myRule);
    Object parse = digester.parse(file);
    System.out.println(parse);
}


/**
 * 测试RuleSetBase
 * 自定义规则类
 */
static class MyRule extends RuleSetBase{

    public void addRuleInstances(Digester digester) {
        try {
            digester.addObjectCreate("leader", Leader.class);
            digester.addSetProperties("leader");
            digester.addObjectCreate("leader/crew", Crew.class);
            digester.addSetProperties("leader/crew");
            /*
             * 参数一:匹配模式
             * 参数二:将第二个对象传递个第一个对象时调用的第一个对象的方法
             */
            digester.addSetNext("leader/crew", "setCrew");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以看到我们自定义的类中的addRuleInstances()方法中的代码和之前测试对象关系的代码完全一样,执行效果也是一样的,所以只是不同方式达到相同的效果而已。

好了Digester常用的功能以及部分实现我们就讨论到这里,之所以要讨论这个第三方包是因为在tomcat源码中也是用了这个类,主要是用来解析server.xmlweb.xml,所以下面一篇文章我们就讲解下Digester在tomcat中的应用。

参考文献:
How tomcat works 相关章节。

posted @ 2016-08-28 23:53  coldridgeValley  阅读(1362)  评论(0编辑  收藏  举报