Digester源码解析(4讲)
2013-02-21 21:21 阅读(171)

本篇主要说下Digester的内置属性是怎样通过相互配合最后生成java Bean的。还是以第一篇的demo为例:

digester.push(new DigesterDemo());		
//指明匹配模式和要创建的类
digester.addObjectCreate( "students/student", Student.class);
//将元素属性也映射到对象中
digester.addSetProperties( "students/student");
//当移动到下一个标签中时的动作
digester.addSetNext( "students/student", "addStudent");
//设置对象属性
digester.addBeanPropertySetter( "students/student/name");
digester.addBeanPropertySetter( "students/student/course");
try {
    //解析结束并返回根元素
    DigesterDemo ds= (DigesterDemo)digester.parse(path);
} catch (Exception e) {
    e.printStackTrace();
}

第一句,先创建个DigesterDemo实例, 并压入堆栈,。第一篇中有DigesterDemo类的完整定义,根据定义可以看到DigesterDemo类里有个"students"集合,用于存放

所有student实例。F5进入:

/**
     * Push a new object onto the top of the object stack.
     *
     * @param object The new object
     */
    public void push(Object object) {

        if (stack.size() == 0) {
            root = object;
        }
        stack.push(object);
    }

stack是个堆栈,专门用于存放解析过程中生成的对象,本例中就是Student对象。if语句先判断堆栈中是否是空的,如果是则将压入的对象赋给root,这个root是单独用于存放根对象的,之后再将其压入堆栈。由此可知digester.push(new DigesterDemo()); 中的DigesterDemo实例就是根对象,最后Digester执行parse()方法解析完xml后会将跟对象返回。


digester.addObjectCreate( "students/student", Student.class);
为匹配模式"students/student"添加"对象创建规则",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));

    }
可以看到,实际上是创建一个ObjectCreateRule规则,并配置到匹配模式"students/studen"下。clazz 就是上面的"Student.class", 即要创建的对象。也就是当ObjectCreateRule被调用时会创建个Student对象。


//将元素属性也映射到对象中
digester.addSetProperties( "students/student");
为匹配模式"students/student"添加"属性规则"。主要作用就是将元素<student>的所有属性值赋到Student中对应的属性上。F5进入:



/**
     * Add a "set properties" rule for the specified parameters.
     *
     * @param pattern Element matching pattern
     * @see SetPropertiesRule
     */
    public void addSetProperties(String pattern) {

        addRule(pattern,
                new SetPropertiesRule());

    }
也是简单的为匹配模式创建个SetPropertiesRule规则。




//当移动到下一个标签中时的动作
digester.addSetNext( "students/student", "addStudent");
上面这句F5进入下可以看到,会为匹配模式"students/student"添加个SetNextRule规则。“addStudent”是个方法名,是DigesterDemo类的方法名,功能是向集合students中添加student对象。由此不难想象SetNextRule规则的作用就是,当执行到此规则时会调用指定的方法(此处是addStudent),通过反射调用。


接下来我们就看下匹配模式(students/student)的几个规则是如何工作的。以下的代码我是按照实际的执行顺序来贴出来的,请注意下。另外需要注意某一匹配模式下的所有规则是严格按顺序执行的,即按照添加的先后顺序。如下俩句:

//指明匹配模式和要创建的类

digester.addObjectCreate( "students/student", Student.class);

//将元素属性也映射到对象中

digester.addSetProperties( "students/student");

上面俩句不能颠倒,即要先通过创建对象规则(ObjectCreateRule)将对象创建出来,否则下面的赋值操作就无法进行。


回归正题:


/**
     * Process the beginning of this element.
     *
     * @param attributes The attribute list of this element
     */
    public void begin(Attributes attributes) throws Exception {

        // Identify the name of the class to instantiate
        String realClassName = className;
        if (attributeName != null) {
            String value = attributes.getValue(attributeName);
            if (value != null) {
                realClassName = value;
            }
        }
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[ObjectCreateRule]{" + digester.match +
                    "}New " + realClassName);
        }

        // Instantiate the new object and push it on the context stack
        Class clazz = digester.getClassLoader().loadClass(realClassName);
        Object instance = clazz.newInstance();
        digester.push(instance);

    }
调用规则ObjectCreateRule的begin()方法。方法的执行时间是在当解析器解析到元素<student>时由startElement()方法调用。attributes是<student>的属性集,className是要创建对象的完整类名,此处是"com.Student",attributeName是属性的名称,属性值和className一样也是要创建类的完整类名,这俩个参数都是在创建ObjectCreateRule实例时,由构造方法传入的。由if (attributeName != null) {。。。}语句可以知道,这俩个属性是存在优先级的,attributeName的优先级大于className。最后三句是由类的完整名创建实例,并压入堆栈,此时堆栈中就已经存在俩个对象了,一个是DigesterDemo对象,一个是Student对象。



/**
     * Process the beginning of this element.
     *
     * @param attributes The attribute list of this element
     */
    public void begin(Attributes attributes) throws Exception {
        
        // Build a set of attribute names and corresponding values
        HashMap values = new HashMap();
        
        // set up variables for custom names mappings
        int attNamesLength = 0;
        if (attributeNames != null) {
            attNamesLength = attributeNames.length;
        }
        int propNamesLength = 0;
        if (propertyNames != null) {
            propNamesLength = propertyNames.length;
        }
        
        
        for (int i = 0; i < attributes.getLength(); i++) {
            String name = attributes.getLocalName(i);
            if ("".equals(name)) {
                name = attributes.getQName(i);
            }
            String value = attributes.getValue(i);
            
            // we'll now check for custom mappings
            for (int n = 0; n<attNamesLength; n++) {
                if (name.equals(attributeNames[n])) {
                    if (n < propNamesLength) {
                        // set this to value from list
                        name = propertyNames[n];
                        // set name to null
} else { // we'll check for this later name = null; } break; } } if (digester.log.isDebugEnabled()) { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Setting property '" + name + "' to '" + value + "'"); } if ((!ignoreMissingProperty) && (name != null)) { // The BeanUtils.populate method silently ignores items in // the map (ie xml entities) which have no corresponding // setter method, so here we check whether each xml attribute // does have a corresponding property before calling the // BeanUtils.populate method. // // Yes having the test and set as separate steps is ugly and // inefficient. But BeanUtils.populate doesn't provide the // functionality we need here, and changing the algorithm which // determines the appropriate setter method to invoke is // considered too risky. // // Using two different classes (PropertyUtils vs BeanUtils) to // do the test and the set is also ugly; the codepaths // are different which could potentially lead to trouble. // However the BeanUtils/ProperyUtils code has been carefully // compared and the PropertyUtils functionality does appear // compatible so we'll accept the risk here. Object top = digester.peek(); boolean test = PropertyUtils.isWriteable(top, name); if (!test) throw new NoSuchMethodException("Property " + name + " can't be set"); } if (name != null) { values.put(name, value); } } // Populate the corresponding properties of the top object Object top = digester.peek(); if (digester.log.isDebugEnabled()) { if (top != null) { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Set " + top.getClass().getName() + " properties"); } else { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Set NULL properties"); } } BeanUtils.populate(top, values); }
调用规则SetPropertiesRule的begin()方法。这个方法的作用是遍历元素的所有属性,调用当前栈顶元素的setXXX()方法进入负责,如果没有setXXX方法则抛出异常,栈顶元素是通过Object top = digester.peek()获得,此方法只是进行访问而不进行出栈


操作。最终的赋值是通过BeanUtils.populate(top, values)完成的,BeanUtils 是apache下commons工具库beanutils中定义的。

接下来应该执行的是规则SetNextRule的begin()方法,但此方法没有重写父类Rule的方法,而父类的begin()方法是空方法。


接下来xml解析器将会解析<student>的子元素<name>Java Boy</name> <course>JSP</course>,通过文章开头的demo可以看到,这俩个子元素对应的匹配模式通过digester.addBeanPropertySetter( "students/student/name");
digester.addBeanPropertySetter( "students/student/course");俩句分别配置了BeanPropertySetterRule规则,这个规则的作用是将当前元素的标签名(即name、course)作为其父元素的属性名、元素的值作为父元素的属性值进行赋值。

看下源码:

/**
     * Process the body text of this element.
     *
     * @param namespace the namespace URI of the matching element, or an 
     *   empty string if the parser is not namespace aware or the element has
     *   no namespace
     * @param name the local name if the parser is namespace aware, or just 
     *   the element name otherwise
     * @param text The text of the body of this element
     */
    public void body(String namespace, String name, String text)
        throws Exception {

        // log some debugging information
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[BeanPropertySetterRule]{" +
                    digester.match + "} Called with text '" + text + "'");
        }

        bodyText = text.trim();

    }
调用规则BeanPropertySetterRule的body()方法。name是元素的标签名,text是元素值,此方法的作用是通过语句bodyText = text.trim();将元素值赋到bodyText中,bodyText是Digester的一个属性,这样BeanPropertySetterRule的end()方法就可以


访问到元素的值。

/**
     * Process the end of this element.
     *
     * @param namespace the namespace URI of the matching element, or an 
     *   empty string if the parser is not namespace aware or the element has
     *   no namespace
     * @param name the local name if the parser is namespace aware, or just 
     *   the element name otherwise
     *
     * @exception NoSuchMethodException if the bean does not
     *  have a writeable property of the specified name
     */
    public void end(String namespace, String name) throws Exception {

        String property = propertyName;

        if (property == null) {
            // If we don't have a specific property name,
            // use the element name.
            property = name;
        }

        // Get a reference to the top object
        Object top = digester.peek();

        // log some debugging information
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[BeanPropertySetterRule]{" + digester.match +
                    "} Set " + top.getClass().getName() + " property " +
                               property + " with text " + bodyText);
        }

        // Force an exception if the property does not exist
        // (BeanUtils.setProperty() silently returns in this case)
        if (top instanceof DynaBean) {
            DynaProperty desc =
                ((DynaBean) top).getDynaClass().getDynaProperty(property);
            if (desc == null) {
                throw new NoSuchMethodException
                    ("Bean has no property named " + property);
            }
        } else /* this is a standard JavaBean */ {
            PropertyDescriptor desc =
                PropertyUtils.getPropertyDescriptor(top, property);
            if (desc == null) {
                throw new NoSuchMethodException
                    ("Bean has no property named " + property);
            }
        }

        // Set the property (with conversion as necessary)
        BeanUtils.setProperty(top, property, bodyText);

    }
调用规则BeanPropertySetterRule的end()方法。首先通过Object top = digester.peek()访问栈顶元素,由于匹配模式"students/student/name""students/student/course"没有配置ObjectCreateRule规则,所以此时堆栈的顶端元素仍然是其父元素


,此处是(Student对象)。接下来的if()else()语句是判断下,判断父元素是否存在这样的属性,没有则抛出异常,有则最后通过BeanUtils.setProperty(top, property, bodyText);语句进行赋值。





接下来依次执行规则ObjectCreateRule的body()、规则SetPropertiesRule的body()、SetNextRule的body()方法,但同样这些方法都是空方法,不进行操作。


 /**
     * Process the end of this element.
     */
    public void end() throws Exception {

        Object top = digester.pop();
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[ObjectCreateRule]{" + digester.match +
                    "} Pop " + top.getClass().getName());
        }

    }
调用规则ObjectCreateRule的end()方法,可以看到此方法唯一一条语句只是从堆栈弹出对象(此处是Student对象),由此可知此对象已经被完整创建并赋值了,否则是不会被弹出堆栈的。



接下来应该执行的是规则SetPropertiesRule的end()方法,但此方法同时是空方法。


/**
     * Process the end of this element.
     */
    public void end() throws Exception {

        // Identify the objects to be used
        Object child = digester.peek(0);
        Object parent = digester.peek(1);
        if (digester.log.isDebugEnabled()) {
            if (parent == null) {
                digester.log.debug("[SetNextRule]{" + digester.match +
                        "} Call [NULL PARENT]." +
                        methodName + "(" + child + ")");
            } else {
                digester.log.debug("[SetNextRule]{" + digester.match +
                        "} Call " + parent.getClass().getName() + "." +
                        methodName + "(" + child + ")");
            }
        }

        // Call the specified method
        Class paramTypes[] = new Class[1];
        if (paramType != null) {
            paramTypes[0] =
                    digester.getClassLoader().loadClass(paramType);
        } else {
            paramTypes[0] = child.getClass();
        }
        
        if (useExactMatch) {
        
            MethodUtils.invokeExactMethod(parent, methodName,
                new Object[]{ child }, paramTypes);
                
        } else {
        
            MethodUtils.invokeMethod(parent, methodName,
                new Object[]{ child }, paramTypes);
        
        }
    }

调用规则SetNextRule的end()方法。首先第1、2句分别访问了堆栈的第一个元素和第二个元素,也即当前元素对应的对象和父元素对应的对象,此时第一个元素的值应该是Student对象,第二个元素的值是DigesterDemo对象。methodName存放的是

方法名,是通过调用digester.addSetNext( "students/student", "addStudent")时传入的 “addStudent”,也即是DigesterDemo类的addStudent(Student student)方法。通过addStudent方法可以将解析过程中<student>元素对应的bean添加进集合中

,并通过最终返回DigesterDemo对象一起返回。


至此整个xml的解析就已经完成了。