本篇主要说下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];调用规则SetPropertiesRule的begin()方法。这个方法的作用是遍历元素的所有属性,调用当前栈顶元素的setXXX()方法进入负责,如果没有setXXX方法则抛出异常,栈顶元素是通过Object top = digester.peek()获得,此方法只是进行访问而不进行出栈// 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); }
操作。最终的赋值是通过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的解析就已经完成了。