Apache Commons Digester 的使用

Learning and Using Jakarta Digester

 

The Jakarta Digester Framework

The Jakarta Digester framework grew out of(脱离) the Jakarta Struts Web toolkit. Originally(起初) developed to process the central struts-config.xml configuration file, it was soon recognized that the framework was more generally useful, and moved to the Jakarta Commons project, the stated(规定的) goal of which is to provide a "repository of reusable Java components(可复用的Java组件)."

The Digester class lets the application programmer specify(指定) a set of actions to be performed(完成) whenever the parser(解析器) encounters(突然遇到) certain(某些) simple patterns(模式) in the XML document. The Digester framework comes with(提供) 10 prepackaged(预先包装的) "rules," which cover most of the required tasks when unmarshalling (解散)XML (such as creating a bean or setting a bean property), but each user is free to define and implement his or her own rules, as necessary.

The Example Document and Beans

In this example, we will unmarshall the same XML document that we used in the previous article:

<?xml version="1.0"?>

 

<catalog library="somewhere">

 

   <book>

      <author>Author 1</author>

      <title>Title 1</title>

   </book>

 

   <book>

      <author>Author 2</author>

      <title>His One Book</title>

   </book>

 

   <magazine>

      <name>Mag Title 1</name>

 

      <article page="5">

         <headline>Some Headline</headline>

      </article>

 

      <article page="9">

         <headline>Another Headline</headline>

      </article>

   </magazine>

 

   <book>

      <author>Author 2</author>

      <title>His Other Book</title>

   </book>

 

   <magazine>

      <name>Mag Title 2</name>

 

      <article page="17">

         <headline>Second Headline</headline>

      </article>

   </magazine>

 

</catalog>

The bean classes are also the same, except for(除了) one important change: In the previous article, I had declared these classes to have package scope -- primarily so that I could define all of them in the same source file! Using the Digester framework, this is no longer possible; the classes need to be declared as public (as is required for classes conforming to(符合) the JavaBeans specification):

import java.util.Vector;

 

public class Catalog {

   private Vector books;

   private Vector magazines;

 

   public Catalog() {

      books = new Vector();

      magazines = new Vector();

   }

 

   public void addBook( Book rhs ) {

      books.addElement( rhs );

   }

   public void addMagazine( Magazine rhs ) {

      magazines.addElement( rhs );

   }

 

   public String toString() {

      String newline = System.getProperty( "line.separator" );

      StringBuffer buf = new StringBuffer();

 

      buf.append( "--- Books ---" ).append( newline );

      for( int i=0; i<books.size(); i++ ){

         buf.append( books.elementAt(i) ).append( newline );

      }

 

      buf.append( "--- Magazines ---" ).append( newline );

      for( int i=0; i<magazines.size(); i++ ){

         buf.append( magazines.elementAt(i) ).append( newline );

      }

 

      return buf.toString();

   }

}


public class Book {

   private String author;

   private String title;

 

   public Book() {}

 

   public void setAuthor( String rhs ) { author = rhs; }

   public void setTitle(  String rhs ) { title  = rhs; }

 

   public String toString() {

      return "Book: Author='" + author + "' Title='" + title + "'";

   }

}


import java.util.Vector;

 

public class Magazine {

   private String name;

   private Vector articles;

 

   public Magazine() {

      articles = new Vector();

   }

 

   public void setName( String rhs ) { name = rhs; }

 

   public void addArticle( Article a ) {

      articles.addElement( a );

   }

 

   public String toString() {

      StringBuffer buf = new StringBuffer( "Magazine: Name='" + name + "' ");

      for( int i=0; i<articles.size(); i++ ){

         buf.append( articles.elementAt(i).toString() );

      }

      return buf.toString();

   }

}


public class Article {

   private String headline;

   private String page;

 

   public Article() {}

 

   public void setHeadline( String rhs ) { headline = rhs; }

   public void setPage(     String rhs ) { page     = rhs; }

 

   public String toString() {

      return "Article: Headline='" + headline + "' on page='" + page + "' ";

   }

}

 

Specifying Patterns and Rules

The Digester class processes the input XML document based on patterns and rules. The patterns must match(匹配) XML elements, based on their name and location(位置) in the document tree. The syntax(语法) used to describe the matching patterns resembles(类似于) the XPath match patterns, a little: the pattern catalog matches the top-level <catalog> element, the pattern catalog/book matches a <book> element nested directly inside a <catalog> element, but nowhere else in the document, etc.

All patterns are absolute: the entire(完整) path from the root element on down has to be specified. The only exception(例外) are patterns containing the wildcard operator *(通配符): the pattern */name will match a <name> element anywhere in the document. Also note that there is no need for a special designation for the root element, since all paths are absolute.

Whenever the Digester encounters(遇到) one of the specified patterns, it performs the actions that have been associated with() it. In this, the Digester framework is of course related to a SAX parser (and in fact, the Digester class implements(实现) org.xml.sax.ContentHandler and maintains(提供) the parse stack). All rules to be used with the Digester must extend org.apache.commons.digester.Rule -- which in itself exposes methods similar to the SAX ContentHandler callbacks: begin() and end() are called when the opening and closing tags of the matched element are encountered.

The body() method is called for the content nested inside of the matched element, and finally, there is a finish() method, which is called once processing of the closing tag is complete, to provide a hook to do possible final clean-up chores. Most application developers will not have to concern themselves with these functions, however, since the standard rules that ship with the framework are likely to provide all desired functionality.

To unmarshal a document, then, create an instance of the org.apache.commons.digester.Digester class, configure it if necessary, specify the required patterns and rules, and finally, pass a reference to the XML file to the parse() method. This is demonstrated in the DigesterDriver class below. (The filename of the input XML document must be specified on the command line.)

import org.apache.commons.digester.*;
 
import java.io.*;
import java.util.*;
 
public class DigesterDriver {
 
   public static void main( String[] args ) {
 
      try {
         Digester digester = new Digester();
         digester.setValidating( false );
 
         digester.addObjectCreate( "catalog", Catalog.class );
 
         digester.addObjectCreate( "catalog/book", Book.class );
         digester.addBeanPropertySetter( "catalog/book/author", "author" );
         digester.addBeanPropertySetter( "catalog/book/title", "title" );
         digester.addSetNext( "catalog/book", "addBook" );
 
         digester.addObjectCreate( "catalog/magazine", Magazine.class );
         digester.addBeanPropertySetter( "catalog/magazine/name", "name" );
 
         digester.addObjectCreate( "catalog/magazine/article", Article.class );
         digester.addSetProperties( "catalog/magazine/article", "page", "page" );
         digester.addBeanPropertySetter( "catalog/magazine/article/headline" ); 
         digester.addSetNext( "catalog/magazine/article", "addArticle" );
 
         digester.addSetNext( "catalog/magazine", "addMagazine" );
 
         File input = new File( args[0] );
         Catalog c = (Catalog)digester.parse( input );
 
         System.out.println( c.toString() );
 
      } catch( Exception exc ) {
         exc.printStackTrace();
      }
   }
}

After instantiating the Digester, we specify that it should not validate the XML document against a DTD -- because we did not define one for our simple Catalog document. Then we specify the patterns and the associated rules: the ObjectCreateRule creates an instance (实例)of the specified class and pushes it onto(压入) the parse stack. The SetPropertiesRule sets a bean property to the value of an XML attribute of the current element -- the first argument to the rule is the name of the attribute, the second, the name of the property.

Whereas SetPropertiesRule takes the value from an attribute, BeanPropertySetterRule takes the value from the raw(原始) character data nested(嵌套) inside of the current element. It is not necessary to specify the name of the property to set when using BeanPropertySetterRule: it defaults to the name of the current XML element. In the example above, this default is being used in the rule definition matching the catalog/magazine/article/headline pattern. Finally, the SetNextRule pops(弹出) the object on top of the parse stack and passes it to the named method on the object below it -- it is commonly used to insert a finished bean into its parent.

Note that it is possible to register several rules for the same pattern. If this occurs, the rules are executed in the order in which they are added to the Digester -- for instance, to deal with the <article> element, found at catalog/magazine/article, we first create the appropriate article bean, then set the page property, and finally pop the completed article bean and insert it into its magazine parent.

Invoking Arbitrary Functions

It is not only possible to set bean properties, but to invoke arbitrary methods on objects in the stack. This is accomplished using the CallMethodRule to specify the method name and, optionally, the number and type of arguments passed to it. Subsequent specifications of the CallParamRule define the parameter values to be passed to the invoked functions. The values can be taken either from named attributes of the current XML element, or from the raw character data contained by the current element. For instance, rather than using the BeanPropertySetterRule in the DigesterDriver implementation above, we could have achieved the same effect by calling the property setter explicitly, and passing the data as parameter:

   digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );
   digester.addCallParam( "catalog/book/author", 0 );

The first line gives the name of the method to call (setAuthor()), and the expected number of parameters (1). The second line says to take the value of the function parameter from the character data contained in the <author> element and pass it as first element in the array of arguments (i.e., the array element with index 0). Had we also specified an attribute name (e.g., digester.addCallParam( "catalog/book/author", 0, "author" );), the value would have been taken from the respective attribute of the current element instead.

One important caveat(警告): confusingly(难以理解地), digester.addCallMethod( "pattern", "methodName", 0 ); does not specify a call to a method taking no arguments – instead(相反), it specifies a call to a method taking one argument, the value of which is taken from the character data of the current XML element! We therefore(因此) have yet another way to implement a replacement(替换) for BeanPropertySetterRule:

   digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );

To call a method that truly takes no parameters, use digester.addCallMethod( "pattern", "methodName" );.

Summary of Standard Rules

Below are brief descriptions of all of the standard rules.

Creational

·         ObjectCreateRule: Creates an object of the specified class using its default constructor and pushes it onto the stack; it is popped when the element completes. The class to instantiate can be given through a class object or the fully-qualified class name.

·         FactoryCreateRule: Creates an object using a specified factory class and pushes it onto the stack. This can be useful for classes that do not provide a default constructor. The factory class must implement the org.apache.commons.digester.ObjectCreationFactory interface.

Property Setters

·         SetPropertiesRule: Sets one or several named properties in the top-level bean using the values of named XML element attributes. Attribute names and property names are passed to this rule in String[] arrays. (Typically used to handle XML constructs like <article page="10">.)

·         BeanPropertySetterRule: Sets a named property on the top-level bean to the character data enclosed by the current XML element. (Example: <page>10</page>.)

·         SetPropertyRule: Sets a property on the top-level bean. Both the property name, as well as the value to which this property will be set, are given as attributes to the current XML element. (Example: <article key="page" value="10" />.)

Parent/Child Management

·         SetNextRule: Pops the object on top of the stack and passes it to a named method on the object immediately below. Typically used to insert a completed bean into its parent.

·         SetTopRule: Passes the second-to-top object on the stack to the top-level object. This is useful if the child object exposes a setParent method, rather than the other way around.

·         SetRootRule: Calls a method on the object at the bottom of the stack, passing the object on top of the stack as argument.

Arbitrary Method Calls

·         CallMethodRule: Calls an arbitrary named method on the top-level bean. The method may take an arbitrary set of parameters. The values of the parameters are given by subsequent applications(后来的申请) of the CallParamRule.

·         CallParamRule: Represents the value of a method parameter. The value of the parameter is either taken from a named XML element attribute, or from the raw character data enclosed by the current element. This rule requires that its position on the parameter list is specified by(被指明) an integer index.

 

你可能感兴趣的:(apache,bean,xml,struts,UP)