手写一个XML解析器:Digester
|字数总计:4.2k|阅读时长:20分钟|阅读量:|
前面我们简单地介绍了如何使用Apache Commons Digester
对XML
进行解析。Digester
是对SAX
的封装,用来解决SAX
解析XML
的一些不足之处。在使用时,我们只需要定义一系列的解析规则,然后便可以创建出对应的对象并进行关联。那么这是怎么实现的呢?
下面,我们通过对一个简单的XML
文件进行解析,一步一步的绘制出我们的XML
解析器框架。
首先,我们知道SAX
是事件驱动的,在使用SAX
进行解析时,我们需要专门定义一个事件处理器继承自DefaultHandler
,当解析到元素时会触发事件处理器的startElement
方法:
1 2 3 4 5 6
| public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { }
|
可以看到元素的名称及属性等会作为方法参数传入,这个时候我们通常需要根据不同的元素名称执行不同的逻辑。比如解析到<Foo>
我们想创建Foo
对象,解析到<Bar>
我们想创建Bar
对象等等。
最简单的方式,我们用if-else
,代码大概是下面这样:
1 2 3 4 5 6 7 8 9 10 11
| public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("Foo".equals(qName)) { Foo foo = new Foo(); } else if ("Bar".equals(qName)) { Bar bar = new Bar(); } ... }
|
但实际情况基本不会这么简单,对象之间大多都会有关联关系,比如一个Foo
会包含多个Bar
(就好比一个班级包含多个学生),假设要解析的XML
是下面这样:
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8"?> <Foo> <Bar name="bar1"> </Bar> <Bar name="bar2"> </Bar> <Bar name="bar3"> </Bar> </Foo>
|
对应的类定义大致如下:
1 2 3 4 5 6 7 8 9 10 11
| public class Foo { List<Bar> barList = new ArrayList<>(); public void addBar(Bar bar) { barList.add(bar); } }
public class Bar { String name; }
|
由于SAX
是逐行进行解析的,如果按照上面的if-else
方式,当我们解析到第一行<Foo>
时会创建一个Foo
对象,解析到第二行<Bar name="bar1">
时会创建一个Bar
对象,但是Bar
对象怎么和Foo
对象关联上呢?也就是说怎么调用Foo
对象的addBar
方法呢?事实上,当解析到第二行创建Bar
对象时已经获取不到解析第一行时创建的Foo
对象的引用了,我们无法将其关联。
很明显,我们需要有一个地方来保存已经创建过的对象引用,那用什么数据类型来保存呢?我们注意到当解析到结束元素</Bar>
或</Foo>
的时候,与之对应的Bar
或Foo
对象就不会再被使用了。根据XML
格式的特点,先定义的<Foo>
标签后结束</Foo>
,后定义的<Bar>
标签先结束</Bar>
,我们很容易联想到应该使用“先进后出”的栈结构来保存对象引用。
根据XML
的结构,当我们解析到<Foo>
元素时,直接创建Foo
对象并压入栈中;当我们解析到<Bar>
元素时,先创建好Bar
对象,然后获取栈顶元素Foo
的对象引用并将Bar
对象进行关联,最后将Bar
对象压入栈中。而当我们解析到任意结束元素的时候,只需弹出栈顶即可。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private final Stack<Object> stack = new Stack<>();
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("Foo".equals(qName)) { Foo foo = new Foo(); stack.push(foo); LOGGER.info("stack push foo:{}", foo); } else if ("Bar".equals(qName)) { Bar bar = new Bar(); for (int i = 0; i < attributes.getLength(); i++) { if ("name".equals(attributes.getQName(i))) { bar.setName(attributes.getValue(i)); } } Foo foo = (Foo) stack.get(0); foo.addBar(bar); stack.push(bar); LOGGER.info("stack push bar:{}", bar); } }
public void endElement(String uri, String localName, String qName) throws SAXException { if ("Foo".equals(qName)) { Foo pop = (Foo) stack.pop(); LOGGER.info("endElement, stack pop Foo:{}", pop); } else if ("Bar".equals(qName)) { Bar pop = (Bar) stack.pop(); LOGGER.info("endElement, stack pop Bar:{}", pop); } }
|
以上方法已经能够解决问题,但if-else
的写法不够优雅,或者说不够通用,我们需要根据每一个XML
的文档结构去实现定制化的逻辑。
下面我们用策略+工厂模式来进行优化。
我们注意到在startElement
和endElement
方法中,对元素名称qName
进行了相同模式的equals
判断。据此,我们可以抽象出我们的策略接口(或者称为元素处理器接口)Processor
:
1 2 3 4 5
| public interface Processor { default void processStart(String qName, Attributes attributes) throws Exception {}; default void processEnd(String qName) throws Exception {}; void setMyDigester(MyDigester myDigester); }
|
由于我们需要对MyDigester
类中的栈结构进行操作,所以我们需要在MyDigester
类中提供对成员变量stack
的相关操作方法,同时让每一个策略接口的实现类持有一个MyDigester
类的引用。据此,我们可以提供出一个抽象的策略实现类AbstractBaseProcessor
,相关主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class MyDigester extends DefaultHandler { private final Stack<Object> stack = new Stack<>();
public <T> void push(T obj) { stack.push(obj); }
@SuppressWarnings("unchecked") public <T> T pop() { return (T) stack.pop(); }
@SuppressWarnings("unchecked") public <T> T get(int index) { return (T) stack.get(index); }
@SuppressWarnings("unchecked") public <T> T peek() { return (T) stack.peek(); }
@SuppressWarnings("unchecked") public <T> T peek(int n) { int index = (stack.size() - 1) - n; return (T) stack.get(index); } ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
public abstract class AbstractBaseProcessor implements Processor { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractBaseProcessor.class); private MyDigester myDigester;
public MyDigester getMyDigester() { return myDigester; }
@Override public void setMyDigester(MyDigester myDigester) { this.myDigester = myDigester; } }
|
下面我们来进行具体的策略实现。对于Foo
标签,策略实现类FooProcessor
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.xml.sax.Attributes;
public class FooProcessor extends AbstractBaseProcessor { @Override public void processStart(String qName, Attributes attributes) throws Exception { Foo foo = new Foo(); getMyDigester().push(foo); LOGGER.info("FooProcessor#processStart stack push foo:{}", foo); }
@Override public void processEnd(String qName) throws Exception { Foo pop = getMyDigester().pop(); LOGGER.info("FooProcessor#processEnd endElement, stack pop Foo:{}", pop); } }
|
对于Bar
标签,策略实现类BarProcessor
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.xml.sax.Attributes;
public class BarProcessor extends AbstractBaseProcessor { @Override public void processStart(String qName, Attributes attributes) { Bar bar = new Bar(); for (int i = 0; i < attributes.getLength(); i++) { if ("name".equals(attributes.getQName(i))) { bar.setName(attributes.getValue(i)); } } Foo foo = getMyDigester().get(0); foo.addBar(bar); getMyDigester().push(bar); LOGGER.info("BarProcessor#processStart stack push bar:{}", bar); }
@Override public void processEnd(String qName) { Bar pop = getMyDigester().pop(); LOGGER.info("BarProcessor#processStart endElement, stack pop Bar:{}", pop); } }
|
接下来,我们定义策略工厂ProcessorFactory
,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import com.google.common.collect.Maps;
import java.util.ArrayList; import java.util.List; import java.util.Map;
public class ProcessorFactory { private final Map<String, List<Processor>> importHfMap = Maps.newHashMap();
public void addProcessor(String qName, Processor processor) { List<Processor> processorList = matchProcessor(qName); if (processorList == null || processorList.size() == 0) { processorList = new ArrayList<>(); } processorList.add(processor); importHfMap.put(qName, processorList); }
public void addProcessorList(String qName, List<Processor> processorList) { List<Processor> mpl = matchProcessor(qName); if (mpl == null || mpl.size() == 0) { mpl = new ArrayList<>(); } mpl.addAll(processorList); importHfMap.put(qName, mpl); }
public List<Processor> matchProcessor(String qName) { return importHfMap.get(qName); }
public static ProcessorFactory newInstance() { return new ProcessorFactory(); } }
|
将不同的qName
和对应的元素处理器Processor
进行一次映射,在使用时我们只需要根据qName
从工厂中获取对应的元素处理器集合,然后执行即可。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class MyDigester extends DefaultHandler { ... private ProcessorFactory processorFactory = null;
public ProcessorFactory getProcessorFactory() { return processorFactory; }
public void setProcessorFactory(ProcessorFactory processorFactory) { this.processorFactory = processorFactory; }
...
@Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { List<Processor> processorList = processorFactory.matchProcessor(qName); for (Processor processor : processorList) { try { processor.processStart(qName, attributes); } catch (Exception e) { e.printStackTrace(); } } }
@Override public void endElement(String uri, String localName, String qName) throws SAXException { List<Processor> processorList = processorFactory.matchProcessor(qName); for (Processor processor : processorList) { try { processor.processEnd(qName); } catch (Exception e) { e.printStackTrace(); } } } ... }
|
一切准备就绪,我们可以进行一个简单的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.IOException; import java.io.InputStream;
public class MyDigesterParseTest { public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { InputStream is = MyDigesterParseTest.class.getResourceAsStream("/xml/FooBar.xml"); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser saxParser = spf.newSAXParser();
MyDigester myDigester = new MyDigester();
ProcessorFactory pf = ProcessorFactory.newInstance();
FooProcessor fooProcessor = new FooProcessor(); fooProcessor.setMyDigester(myDigester); pf.addProcessor("Foo", fooProcessor);
BarProcessor barProcessor = new BarProcessor(); barProcessor.setMyDigester(myDigester); pf.addProcessor("Bar", barProcessor);
myDigester.setProcessorFactory(pf);
saxParser.parse(is, myDigester); } }
|
运行结果如下:
1 2 3 4 5 6 7 8 9 10
| 01:12:17.890 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.MyDigester - MyDigester.startDocument 01:12:17.896 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - FooProcessor#processStart stack push foo:Foo{barList=[]} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processStart stack push bar:Bar{name='bar1'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processEnd endElement, stack pop Bar:Bar{name='bar1'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processStart stack push bar:Bar{name='bar2'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processEnd endElement, stack pop Bar:Bar{name='bar2'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processStart stack push bar:Bar{name='bar3'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - BarProcessor#processEnd endElement, stack pop Bar:Bar{name='bar3'} 01:12:17.898 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - FooProcessor#processEnd endElement, stack pop Foo:Foo{barList=[Bar{name='bar1'}, Bar{name='bar2'}, Bar{name='bar3'}]} 01:12:17.899 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.MyDigester - MyDigester.endDocument
|
可以看到,一切运作正常。但好像还是有那么一点不完美,对于不同的XML
文件,我们还是需要去写定制化的Processor
实现类,同时我们注意到在BarProcessor
中,对于Bar
对象的属性,我们是通过遍历attributes
参数进行设置的,另外,addBar
方法也是我们主动调用的。怎么实现更加通用的Processor
呢?
想要变得通用,我们就不该去关注具体的XML
元素Foo
和Bar
,而是应该关注我们要做什么。我们要创建对象,要给对象的属性赋值,要调用对象的某个方法。而这些,在Java
语言中都可以通过反射来实现。于是我们的Processor
实现类应该是:对象创建实现类ObjectCreateProcessor
、属性赋值实现类SetPropertiesProcessor
和方法调用实现类InvokeMethodProcessor
。下面我们来依次进行实现:
对象创建实现类ObjectCreateProcessor
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.xml.sax.Attributes;
public class ObjectCreateProcessor extends AbstractBaseProcessor { private final String className;
public ObjectCreateProcessor(String className) { this.className = className; }
@Override public void processStart(String qName, Attributes attributes) throws Exception { Object instance = this.getMyDigester().getClass().getClassLoader().loadClass(className).newInstance(); getMyDigester().push(instance); LOGGER.info("ObjectCreateProcessor#processStart stack push:{}", instance); }
@Override public void processEnd(String qName) throws Exception { Object pop = getMyDigester().pop(); LOGGER.info("ObjectCreateProcessor#processEnd stack pop:{}", pop); } }
|
属性赋值实现类SetPropertiesProcessor
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import com.google.common.collect.Maps; import org.apache.commons.beanutils.BeanUtils; import org.xml.sax.Attributes;
import java.util.Map;
public class SetPropertiesProcessor extends AbstractBaseProcessor { @Override public void processStart(String qName, Attributes attributes) throws Exception { Map<String, String> properties = Maps.newHashMap(); for (int i = 0; i < attributes.getLength(); i++) { properties.put(attributes.getQName(i), attributes.getValue(i)); } Object top = getMyDigester().peek(); LOGGER.info("SetPropertiesProcessor#processStart stack peek:{}", top); BeanUtils.populate(top, properties); } }
|
方法调用实现类InvokeMethodProcessor
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.sunchaser.sparrow.javase.base.xml.mydigester;
import org.apache.commons.beanutils.MethodUtils; import org.xml.sax.Attributes;
public class InvokeMethodProcessor extends AbstractBaseProcessor { private final String methodName; private final String paramType;
public InvokeMethodProcessor(String methodName, String paramType) { this.methodName = methodName; this.paramType = paramType; }
@Override public void processStart(String qName, Attributes attributes) throws Exception { Class<?>[] paramTypes = new Class<?>[1]; paramTypes[0] = getMyDigester().getClass().getClassLoader().loadClass(paramType); Object arg = getMyDigester().peek(0); Object obj = getMyDigester().peek(1); LOGGER.info("InvokeMethodProcessor#processStart invoke obj:{} method:{} with args:{}", obj, methodName, arg); MethodUtils.invokeMethod(obj, methodName, new Object[]{ arg }, paramTypes); } }
|
接下来我们就可以进行简单的测试了,在这之前,为了避免对每一个Processor
实现类都调用setMyDigester
方法,我们可以在MyDigester
中封装一下对Processor
实现类的添加方法,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ... public void addProcessor(String qName, Processor processor) { processor.setMyDigester(this); getProcessorFactory().addProcessor(qName, processor); }
public void addObjectCreate(String qName, String className) { addProcessor(qName, new ObjectCreateProcessor(className)); }
public void addSetProperties(String qName) { addProcessor(qName, new SetPropertiesProcessor()); }
public void addInvokeMethod(String qName, String methodName, String paramType) { addProcessor(qName, new InvokeMethodProcessor(methodName, paramType)); } ...
|
接下来我们就可以进行测试了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { InputStream is = MyDigesterParseTest.class.getResourceAsStream("/xml/FooBar.xml"); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser saxParser = spf.newSAXParser();
MyDigester myDigester = new MyDigester();
ProcessorFactory pf = ProcessorFactory.newInstance();
myDigester.setProcessorFactory(pf); myDigester.addObjectCreate("Foo", "com.sunchaser.sparrow.javase.base.xml.mydigester.Foo"); myDigester.addObjectCreate("Bar", "com.sunchaser.sparrow.javase.base.xml.mydigester.Bar"); myDigester.addSetProperties("Bar"); myDigester.addInvokeMethod("Bar", "addBar", "com.sunchaser.sparrow.javase.base.xml.mydigester.Bar");
saxParser.parse(is, myDigester); }
|
运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 01:13:10.167 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.MyDigester - MyDigester.startDocument 01:13:10.172 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processStart stack push:Foo{barList=[]} 01:13:10.175 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processStart stack push:Bar{name='null'} 01:13:10.175 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - SetPropertiesProcessor#processStart stack peek:Bar{name='null'} ...... 01:13:10.223 [main] DEBUG org.apache.commons.beanutils.BeanUtils - BeanUtils.populate(Bar{name='null'}, {name=bar1}) 01:13:10.230 [main] DEBUG org.apache.commons.beanutils.ConvertUtils - Convert string 'bar1' to class 'java.lang.String' 01:13:10.230 [main] DEBUG org.apache.commons.beanutils.converters.StringConverter - Converting 'String' value 'bar1' to type 'String' 01:13:10.231 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - InvokeMethodProcessor#processStart invoke obj:Foo{barList=[]} method:addBar with args:Bar{name='bar1'} 01:13:10.232 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processEnd stack pop:Bar{name='bar1'} 01:13:10.232 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processStart stack push:Bar{name='null'} 01:13:10.232 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - SetPropertiesProcessor#processStart stack peek:Bar{name='null'} 01:13:10.232 [main] DEBUG org.apache.commons.beanutils.BeanUtils - BeanUtils.populate(Bar{name='null'}, {name=bar2}) 01:13:10.232 [main] DEBUG org.apache.commons.beanutils.ConvertUtils - Convert string 'bar2' to class 'java.lang.String' 01:13:10.232 [main] DEBUG org.apache.commons.beanutils.converters.StringConverter - Converting 'String' value 'bar2' to type 'String' 01:13:10.232 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - InvokeMethodProcessor#processStart invoke obj:Foo{barList=[Bar{name='bar1'}]} method:addBar with args:Bar{name='bar2'} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processEnd stack pop:Bar{name='bar2'} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processStart stack push:Bar{name='null'} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - SetPropertiesProcessor#processStart stack peek:Bar{name='null'} 01:13:10.233 [main] DEBUG org.apache.commons.beanutils.BeanUtils - BeanUtils.populate(Bar{name='null'}, {name=bar3}) 01:13:10.233 [main] DEBUG org.apache.commons.beanutils.ConvertUtils - Convert string 'bar3' to class 'java.lang.String' 01:13:10.233 [main] DEBUG org.apache.commons.beanutils.converters.StringConverter - Converting 'String' value 'bar3' to type 'String' 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - InvokeMethodProcessor#processStart invoke obj:Foo{barList=[Bar{name='bar1'}, Bar{name='bar2'}]} method:addBar with args:Bar{name='bar3'} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processEnd stack pop:Bar{name='bar3'} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.AbstractBaseProcessor - ObjectCreateProcessor#processEnd stack pop:Foo{barList=[Bar{name='bar1'}, Bar{name='bar2'}, Bar{name='bar3'}]} 01:13:10.233 [main] INFO com.sunchaser.sparrow.javase.base.xml.mydigester.MyDigester - MyDigester.endDocument
|
至此,我们就实现了一个较为通用的XML
解析器了,对于不同的XML
文件,我们不再需要去写定制化的Processor
实现类。
当我们要创建对象时,只需调用MyDigester#addObjectCreate
方法,传入指定qName
和要创建的对象的全限定类名即可;同样地,当我们要给对象设置属性时,我们只需调用MyDigester#addSetProperties
方法指定qName
即可;当我们要调用对象的指定方法时,只需调用MyDigester#addInvokeMethod
方法传入指定qName
方法名和方法参数类型即可。