本篇主要说下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的解析就已经完成了。