在Mybatis中设计到多个xml文件,就得接触到xml解析的内容。
xml解析常见的方式有三种,DOM,SAX,StAX
树型结构,搜索快,添加修改也快,找自己的关系节点也快,但是呢在运行的时候需要把整个XML文档放入内存当中,搭建树形结构,所以如果xml中的数据量比较大,就会造成较大的资源浪费。
流式处理,只将一部分xml文档加载到内存中,资源占用较小,而且程序处理模式就像深搜一样,达成自己的目的就立即退出,不必解析剩余的。
解析节点的时候出发节点上面的回调函数,回调函数是由开发人员自己注册的。这种方式也被称为“推模式”。
SAX的确实就是由于不储存XML文档,所以需要开发人员自己去维护节点的关系,工作量比较大。然后是流式处理,只能从前往后单向处理。无法支持XPATH。
是JKD提供的解析XML的API,和SAX类似,也是流式处理,但是StAX采用的是“拉模式”,应用程序控制着整个解析的过程,也就是“用到的时候再去找”。
提供了两套API,一套是指针的,一套迭代器的。
之前学爬虫的时候用过,就是通过一些匹配方式去匹配HTML文档中的节点,类似正则表达。,那么对于XML文档,配合着DOM解析方式使用,就能实现对XML文档的解析,也是同样的道理。
XPathParser
Mybatis提供的XPathParser封装了XPath、Document、EntityResolver,
XpathParser中各个字段的含义和功能:
private Document document; //Document文本对象
private boolean validation;//是否开启验证
private EntityResolver entityResolver;//用于加载本地DTD文件
private Properties variables;//properties中的键值对集合
private XPath xpath;//xpath对象
开始对XML文档进行验证的时候,会根据XML文档开始位置指定的网址加载对应的DTD或者XSD文件,
但是如果网太慢了就会导致验证过程太慢,所以就需要提前设置EntityResolver接口对象加载本地的DTD文件。
XMLMapperEntityResolver是Mybatis提供的EntityResolver接口实现类。代码挺长就不放来了,我总结的这个实现类干的事情就是 :
通过匹配SystemId指定的是哪个DTD文档,去通过getInputSource()把该DTD转化成InputSource对象。
解决完EntityResolver之后呢,XPthParse中的createDocument()方法封装了创建Document对象的过程,并且出发了加载XML文档的过程:
//初始化
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor 这里是让用这个方法之前一定要用上面的初始化一下
try {
//创建DBF对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//创建DB对象
DocumentBuilder builder = factory.newDocumentBuilder();
//设置EntityResolver接口对象
builder.setEntityResolver(entityResolver);
//空实现的异常处理
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
//加载XML文档
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
然后就是XPth部分了。
在XPathParser中提供了一系列的eval*()方法用于解析不同boolean、short、long、int、string等类型的信息,代码都比较简单,如int:
public Integer evalInteger(String expression) {
return evalInteger(document, expression);
}
public Integer evalInteger(Object root, String expression) {
return Integer.valueOf(evalString(root, expression));
}
值得一提的是XPathParser.evalString()方法,其中会调用PropertyParser.parse()方法处理节点中的键值对映射(把key转换成value)。
这个方法里面会创建一个GenericTokenParser解析器,这个解析器里面有三个字段
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
从上到下分别是占位符开始标记,占位符结束标记,实现的Tokenhandler用于解析占位符。
比如 开 始 标 记 就 是 “ { } 开始标记就是“ 开始标记就是“{”,结束标记就是“}”,然后handler就是把键值对里面的key换成value。
GenerpicTokenParser.parse()整个算法的流程就是这样的:
通过开始标记和结束标记去确定原字符串里面需要替换的地方,然后通过Tokenhandler把key换成value,再按原来的顺序把字符串拼接好。
比如传过来的字符串是"‘name’: u s e r n a m e , ′ p a s s w o r d ′ : {user_name},'password': username,′password′:{pw}",然后我们的键值对关系是"user_name":“lyc”,“pw”:“123”,
那么经过这个parse()方法之后,原字符串就变成了:"‘name’:lyc,‘password’:123"。
占位符的解析是通过TokenHandler接口实现的,所以整个过程中的具体解析行为可以根据持有的TokenHandler的不同而有所不同。
最后呢,还有一个XNode,它是对org.w3c.dom.Node对象的封装和解析。
private Node node;//org.w3c.dom.Node对象
private String name;//Node节点对象
private String body;//节点的内容
private Properties attributes;//节点属性集合
private Properties variables;//键值对
private XPathParser xpathParser;//这就是前面介绍的xpathParser
XNode中的构造函数会调用parseAttributes()和parseBody()方法解析Node对象中的信息,对attributes和body字段初始化,另外呢,XNode中提供了多种get*()方法获取所需的节点信息,也实现了XPthParser对象的eval*()方法。
就到这里啦,也算挺有收获的今天。