Digester源码解析(2讲)
2013-02-17 21:19 阅读(196)

接着上一篇。这篇我们来分析有关匹配模式规则配置,及规则管理的源码。

匹配模式(matching pattern)的规则(rule)是如何配置上去的,以创建对象规则ObjectCreateRule为例,如下:

//指明匹配模式和要创建的类
digester.addObjectCreate( "students/student", Student.class);


F5进入下,可以看到:


 /**
     * 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));

    }

addObjectCreate()方法内部创建一个以Student.class(类型对象)作为构造方法参数的ObjectCreateRule对象,然后连同匹配模式pattern一起作为参数调用addRule()方法。addRule()方法是Digester内专门用于为匹配模式配置规则的方法,它的第一个参数

是匹配模式,第二个参数是规则,即ObjectCreateRule是个规则,查看源码可以看到ObjectCreateRule继承了org.apache.commons.digester.Rule类,所有继承该类的都是规则。ObjectCreateRule规则的作用是生成对象的,参数clazz就是要生成的对象。F5进入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);

    }

rule.setDigester(this); 将自身添加到rule的digester属性中,以后会有用。getRules().add(pattern, rule); 首先是得到Digester的规则库(用于存放所有规则的地方),然后将当前规则add 进去。 F5进入getRules()



/**
     * 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);

    }
首相判断下this.rules(规则库)是否是空的,不是则直接返回规则库,是则创建个新的。this.rules = new RulesBase();由这句可以看到规则库实际上是个RulesBase实例,进入RulesBase这个类看下:


public class RulesBase implements Rules {
    protected HashMap cache = new HashMap();
    protected Digester digester = null;
    protected String namespaceURI = null;
    protected ArrayList rules = new ArrayList();
    public void add(String pattern, Rule rule) {
    . 
    .
    .
    }
    public List match(String namespaceURI, String pattern) {
    . 
    .
    .
    }
    protected List lookup(String namespaceURI, String pattern) {
    . 
    .
    .
    }

}

上面的代码是我经过整理的。其中有个属性"cache",这个就是上面所说的规则库(rules),是个哈希表。它的key值就是匹配模式(matching pattern),value值是个List,存放这个匹配模式下的所有规则。可以看到匹配模式的真正作用是作为key值的。

其中还有个属性"rules",它和cache一样也是存放所有规则的,区别是它的类型是个有序表(ArrayList),在Digester里它只有一个作用,当Digester执行到endDocument()时,这时所有的规则同时都要执行finish()方法,如果遍历cache,由于cache是哈希表,是无序的,不能保证按先进先执行的规律执行,所以又单独创建了rules属性。

再来看下它的add()方法,如下:


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

前三句的作用是去掉匹配模式最后的"/",如果有的话。即将"students/student/"变为"students/student"。第四句是根据匹配模式pattern从cache中找到该模式下的用于存放规则的list,如果没有则创建,然后将规则add到list中,同时也add到rules属性中。最下面俩句if 是设置rule, 有关命名空间(Namespace)的设置我就不说了,因为如果只是用Digester的内置规则的话,命名空间是用不到的,除非自己创建新的规则。另外注意:cache的value是ArrayList实例,所以规则的执行是严格按序的,即按添加进去的先后顺序的。


 /**
     * Return a List of all registered Rule instances that match the specified
     * nesting pattern, or a zero-length List if there are no matches.  If more
     * than one Rule instance matches, they <strong>must</strong> be returned
     * in the order originally registered through the <code>add()</code>
     * method.
     *
     * @param namespaceURI Namespace URI for which to select matching rules,
     *  or <code>null</code> to match regardless of namespace URI
     * @param pattern Nesting pattern to be matched
     */
    public List match(String namespaceURI, String pattern) {

        // List rulesList = (List) this.cache.get(pattern);
        List rulesList = lookup(namespaceURI, pattern);
        if ((rulesList == null) || (rulesList.size() < 1)) {
            // Find the longest key, ie more discriminant
            String longKey = "";
            Iterator keys = this.cache.keySet().iterator();
            while (keys.hasNext()) {
                String key = (String) keys.next();
                if (key.startsWith("*/")) {
                    if (pattern.equals(key.substring(2)) ||
                        pattern.endsWith(key.substring(1))) {
                        if (key.length() > longKey.length()) {
                            // rulesList = (List) this.cache.get(key);
                            rulesList = lookup(namespaceURI, key);
                            longKey = key;
                        }
                    }
                }
            }
        }
        if (rulesList == null) {
            rulesList = new ArrayList();
        }
        return (rulesList);
    }

match()是个匹配方法,实际上就是get。第一句是通过命名空间(namespaceURI)、匹配模式(pattern)来检索规则rulesList,通过lookup()方法,这个方法会在下文讲解。如果检索出则直接返回,否则需要进一步进行模糊查找,也就是通过通配符(*)。

Iterator keys = this.cache.keySet().iterator(); while (keys.hasNext()) {....} 遍历cache的key值,也就是遍历所有匹配模式。if (key.startsWith("*/")) {...} 判断匹配模式是否有以"*/"为开头的,由此可知"*"是个通配符。如果有则进一步判断。

if (pattern.equals(key.substring(2)) || pattern.endsWith(key.substring(1))) {...} 由这句话可知,通配符"*"可以取空值或任意字符,"pattern.equals(key.substring(2))" 判断当通配符代表空值时,"pattern.endsWith(key.substring(1))"判断通配符代表

非空字符时。if (key.length() > longKey.length()){...}  这句话的目的是从所有满足条件的匹配模式中选出字符长度最大的,因为字符越长所表示的匹配模式越趋于精确,如:当前pattern为"students/student/name", cache中有俩个带有通配符的匹配模式 "*/name" 、"*/students/student/name"  很显然"*/students/student/name" 更准确些。

/**
     * Return a List of Rule instances for the specified pattern that also
     * match the specified namespace URI (if any).  If there are no such
     * rules, return <code>null</code>.
     *
     * @param namespaceURI Namespace URI to match, or <code>null</code> to
     *  select matching rules regardless of namespace URI
     * @param pattern Pattern to be matched
     */
    protected List lookup(String namespaceURI, String pattern) {

        // Optimize when no namespace URI is specified
        List list = (List) this.cache.get(pattern);
        if (list == null) {
            return (null);
        }
        if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
            return (list);
        }

        // Select only Rules that match on the specified namespace URI
        ArrayList results = new ArrayList();
        Iterator items = list.iterator();
        while (items.hasNext()) {
            Rule item = (Rule) items.next();
            if ((namespaceURI.equals(item.getNamespaceURI())) ||
                    (item.getNamespaceURI() == null)) {
                results.add(item);
            }
        }
        return (results);
    }
lookup()执行具体的检索工作。第一句直接通过pattern从cache中得到此匹配模式下的所有规则,如果该pattern下没有配置规则,则直接返回null。

接下来这几句是对检索出的规则进行命名空间的验证。 由源码可知,当RulesBase、规则(Rule)同时设置了命名空间时,才进行命名空间的一致性验证。否则是不验证的。


总结下上面为匹配模式"students/student"添加ObjectCreateRule规则的过程:


调用digester.addObjectCreate( "students/student", Student.class)后, Digester创建ObjectCreateRule实例,调用规则配置方法addRule()将规则添加到规则库RulesBase中,规则库RulesBase通过add()方法在将规则添加进cache中。由于cache是个
哈希表,这样当系统解析时就可以通过匹配模式检索出其下的所有规则。


待续