目录
1. 是什么
2. The Object Stack
3. Element Matching Patterns
4. Processing Rules
5. 示例
1. 是什么
Many projects read XML configuration files to provide initialization of various Java objects within the system. There are several ways of doing this, and the Digester component was designed to provide a common implementation that can be used in many different projects.
Basically, the Digester package lets you configure an XML -> Java object mapping module, which triggers certain actions called rules whenever a particular pattern of nested XML elements is recognized. A rich set of predefined rules is available for your use, or you can also create your own.
In many application environments that deal with XML-formatted data, it is useful to be able to process an XML document in an "event driven" manner, where particular Java objects are created (or methods of existing objects are invoked) when particular patterns of nested XML elements have been recognized. Developers familiar with the Simple API for XML Parsing (SAX) approach to processing XML documents will recognize that the Digester provides a higher level, more developer-friendly interface to SAX events, because most of the details of navigating the XML element hierarchy are hidden -- allowing the developer to focus on the processing to be performed.
2. The Object Stack
One very common use of org.apache.commons.digester3.Digester technology is to dynamically construct a tree of Java objects, whose internal organization, as well as the details of property settings on these objects, are configured based on the contents of the XML document. In fact, the primary reason that the Digester package was created (it was originally part of Struts, and then moved to the Commons project because it was recognized as being generally useful) was to facilitate the way that the Struts controller servlet configures itself based on the contents of your application's struts-config.xml file.
To facilitate this usage, the Digester exposes a stack that can be manipulated by processing rules that are fired when element matching patterns are satisfied. The usual stack-related operations are made available, including the following:
- clear() - Clear the current contents of the object stack.
- peek() - Return a reference to the top object on the stack, without removing it.
- pop() - Remove the top object from the stack and return it.
- push() - Push a new object onto the top of the stack.
A typical design pattern, then, is to fire a rule that creates a new object and pushes it on the stack when the beginning of a particular XML element is encountered.The object will remain there while the nested content of this element is processed, and it will be popped off when the end of the element is encountered. As we will see, the standard "object create" processing rule supports exactly this functionalility in a very convenient way.
Several potential issues with this design pattern are addressed by other features of the Digester functionality:
- How do I relate the objects being created to each other? - The Digester supports standard processing rules that pass the top object on the stack as an argument to a named method on the next-to-top object on the stack (or vice versa). This rule makes it easy to establish parent-child relationships between these objects. One-to-one and one-to-many relationships are both easy to construct.
- How do I retain a reference to the first object that was created? As you review the description of what the "object create" processing rule does, it would appear that the first object you create (i.e. the object created by the outermost XML element you process) will disappear from the stack by the time that XML parsing is completed, because the end of the element would have been encountered. However, Digester will maintain a reference to the very first object ever pushed onto the object stack, and will return it to you as the return value from the parse() call. Alternatively, you can push a reference to some application object onto the stack before calling parse(), and arrange that a parent-child relationship be created (by appropriate processing rules) between this manually pushed object and the ones that are dynamically created. In this way, the pushed object will retain a reference to the dynamically created objects (and therefore all of their children), and will be returned to you after the parse finishes as well.
3. Element Matching Patterns
A primary feature of the org.apache.commons.digester3.Digester parser is that the Digester automatically navigates the element hierarchy of the XML document you are parsing for you, without requiring any developer attention to this process. Instead, you focus on deciding what functions you would like to have performed whenver a certain arrangement of nested elements is encountered in the XML document being parsed. The mechanism for specifying such arrangements are called element matching patterns.
The Digester can be configured to use different pattern-matching algorithms via the Digester.setRules method. However for the vast majority of cases, the default matching algorithm works fine. The default pattern matching behaviour is described below.
A very simple element matching pattern is a simple string like "a". This pattern is matched whenever an <a> top-level element is encountered in the XML document, no matter how many times it occurs. Note that nested <a> elements will not match this pattern -- we will describe means to support this kind of matching later.
The next step up in matching pattern complexity is "a/b". This pattern will be matched when a <b> element is found nested inside a top-level <a> element. Again, this match can occur as many times as desired, depending on the content of the XML document being parsed. You can use multiple slashes to define a hierarchy of any desired depth that will be matched appropriately.
For example, assume you have registered processing rules that match patterns "a", "a/b", and "a/b/c". For an input XML document with the following contents, the indicated patterns will be matched when the corresponding element is parsed:
代码语言:javascript复制 <a> -- Matches pattern "a"
<b> -- Matches pattern "a/b"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
</b>
<b> -- Matches pattern "a/b"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
<c/> -- Matches pattern "a/b/c"
</b>
</a>
It is also possible to match a particular XML element, no matter how it is nested (or not nested) in the XML document, by using the "*" wildcard character in your matching pattern strings. For example, an element matching pattern of "*/a" will match an <a> element at any nesting position within the document.
It is quite possible that, when a particular XML element is being parsed, the pattern for more than one registered processing rule will be matched because you registered more than one processing rule with the exact same matching pattern.
When this occurs, the corresponding processing rules will all be fired in order. Rule methods begin (and body) are executed in the order that the Rules were initially registered with the Digester, whilst end method calls are executed in reverse order. In other words - the order is first in, last out.
Note that wildcard patterns are ignored if an explicit match can be found (and when multiple wildcard patterns match, only the longest, ie most explicit, pattern is considered a match). The result is that rules can be added for "an <a> tag anywhere", but then for that behaviour to be explicitly overridden for specific cases, eg "but not an <a> that is a direct child of an <x>". Therefore if you have rules A and B registered for pattern "*/a" then want to add an additional rule C for pattern "x/a" only, then what you need to do is add *three* rules for "x/a": A, B and C. Note that by using:
代码语言:javascript复制 Rule ruleA = new ObjectCreateRule();
Rule ruleB = new SetNextRule();
Rule ruleC = new SetPropertiesRule();
digester.addRule("*/a", ruleA);
digester.addRule("*/a", ruleB);
digester.addRule("x/a", ruleA);
digester.addRule("x/a", ruleB);
digester.addRule("x/a", ruleC);
4. Processing Rules
The previous section documented how you identify when you wish to have certain actions take place. The purpose of processing rules is to define what should happen when the patterns are matched.
Formally, a processing rule is a Java class that subclasses the org.apache.commons.digester3.Rule interface. Each Rule implements one or more of the following event methods that are called at well-defined times when the matching patterns corresponding to this rule trigger it:
- begin() - Called when the beginning of the matched XML element is encountered. A data structure containing all of the attributes corresponding to this element are passed as well.
- body() - Called when nested content (that is not itself XML elements) of the matched element is encountered. Any leading or trailing whitespace will have been removed as part of the parsing process.
- end() - Called when the ending of the matched XML element is encountered. If nested XML elements that matched other processing rules was included in the body of this element, the appropriate processing rules for the matched rules will have already been completed before this method is called.
- finish() - Called when the parse has been completed, to give each rule a chance to clean up any temporary data they might have created and cached.
As you are configuring your digester, you can call the addRule() method to register a specific element matching pattern, along with an instance of a Rule class that will have its event handling methods called at the appropriate times, as described above. This mechanism allows you to create Rule implementation classes dynamically, to implement any desired application specific functionality.
In addition, a set of processing rule implementation classes are provided, which deal with many common programming scenarios. These classes include the following:
- ObjectCreateRule - When the begin() method is called, this rule instantiates a new instance of a specified Java class, and pushes it on the stack. The class name to be used is defaulted according to a parameter passed to this rule's constructor, but can optionally be overridden by a classname passed via the specified attribute to the XML element being processed. When the end() method is called, the top object on the stack (presumably, the one we added in the begin() method) will be popped, and any reference to it (within the Digester) will be discarded.
- FactoryCreateRule - A variation of ObjectCreateRule that is useful when the Java class with which you wish to create an object instance does not have a no-arguments constructor, or where you wish to perform other setup processing before the object is handed over to the Digester.
- SetPropertiesRule - When the begin() method is called, the digester uses the standard Java Reflection API to identify any JavaBeans property setter methods (on the object at the top of the digester's stack) who have property names that match the attributes specified on this XML element, and then call them individually, passing the corresponding attribute values. These natural mappings can be overridden. This allows (for example) a class attribute to be mapped correctly. It is recommended that this feature should not be overused - in most cases, it's better to use the standard BeanInfo mechanism. A very common idiom is to define an object create rule, followed by a set properties rule, with the same element matching pattern. This causes the creation of a new Java object, followed by "configuration" of that object's properties based on the attributes of the same XML element that created this object.
- SetPropertyRule - When the begin() method is called, the digester calls a specified property setter (where the property itself is named by an attribute) with a specified value (where the value is named by another attribute), on the object at the top of the digester's stack. This is useful when your XML file conforms to a particular DTD, and you wish to configure a particular property that does not have a corresponding attribute in the DTD.
- SetNextRule - When the end() method is called, the digester analyzes the next-to-top element on the stack, looking for a property setter method for a specified property. It then calls this method, passing the object at the top of the stack as an argument. This rule is commonly used to establish one-to-many relationships between the two objects, with the method name commonly being something like "addChild".
- SetTopRule - When the end() method is called, the digester analyzes the top element on the stack, looking for a property setter method for a specified property. It then calls this method, passing the next-to-top object on the stack as an argument. This rule would be used as an alternative to a SetNextRule, with a typical method name "setParent", if the API supported by your object classes prefers this approach.
- CallMethodRule - This rule sets up a method call to a named method of the top object on the digester's stack, which will actually take place when the end() method is called. You configure this rule by specifying the name of the method to be called, the number of arguments it takes, and (optionally) the Java class name(s) defining the type(s) of the method's arguments. The actual parameter values, if any, will typically be accumulated from the body content of nested elements within the element that triggered this rule, using the CallParamRule discussed next.
- CallParamRule - This rule identifies the source of a particular numbered (zero-relative) parameter for a CallMethodRule within which we are nested. You can specify that the parameter value be taken from a particular named attribute, or from the nested body content of this element.
- NodeCreateRule - A specialized rule that converts part of the tree into a DOM Node and then pushes it onto the stack.
5. 示例
Department.java:
代码语言:javascript复制package webj2eedev;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Department {
private String name;
private String code;
private List<User> users = new ArrayList<User>();
private Map<String, String> extension = new HashMap<String, String>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void addUser(User user) {
this.users.add(user);
}
public void putExtension(String name, String value) {
this.extension.put(name, value);
}
@Override
public String toString() {
return "Department{"
"name='" name '''
", code='" code '''
", users=" users
", extension=" extension
'}';
}
}
User.java:
代码语言:javascript复制package webj2eedev;
public class User {
private String name;
private String code;
public Department getWhereAmI() {
return whereAmI;
}
public void setWhereAmI(Department whereAmI) {
this.whereAmI = whereAmI;
}
private Department whereAmI;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "User{"
"name='" name '''
", code='" code '''
", whereAmI='" whereAmI.getName() '''
'}';
}
}
demo.xml:
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8" ?>
<department name="departmentA" code="department001">
<user name="userA" code="user001"/>
<user name="userB" code="user002"/>
<extension>
<property-name>director</property-name>
<property-value>joke</property-value>
</extension>
</department>
App.java:
代码语言:javascript复制package webj2eedev;
import org.apache.commons.digester3.Digester;
import org.xml.sax.SAXException;
import java.io.IOException;
public class App {
public static void main(String[] args) throws IOException, SAXException {
Digester digester = new Digester();
digester.setValidating(false);
// 匹配到department节点时,创建Department对象,并设置对象属性
digester.addObjectCreate("department", Department.class);
digester.addSetProperties("department");
// 匹配到department/user节点时,创建User对象,并设置对象属性,
// 并通过 Department.addUser 方法追加为 Department 的子对象
// 并通过 User.setWhereAmI 方法设置其父对象为 Department
digester.addObjectCreate("department/user", User.class);
digester.addSetProperties("department/user");
digester.addSetNext("department/user", "addUser");
digester.addSetTop("department/user", "setWhereAmI");
// 匹配到department/extension节点时,在解析结束时,通过 Department.putExtension 传入
digester.addCallMethod("department/extension", "putExtension" ,2);
digester.addCallParam("department/extension/property-name", 0);
digester.addCallParam("department/extension/property-value", 1);
Department department = (Department)digester.parse(App.class.getResourceAsStream("/demo.xml"));
System.out.println(department);
}
}
参考:
Apache Commons Digester: https://commons.apache.org/proper/commons-digester/