写在前面,这里会有很多借鉴的内容,有以下三个原因
- 本博客只是作为本人学习记录并用以分享,并不是专业的技术型博客
- 笔者是位刚刚开始尝试阅读源码的人,对源码的阅读流程乃至整体架构并不熟悉,观看他人博客可以帮助我快速入门
- 如果只是笔者自己观看,难免会有很多弄不懂乃至理解错误的地方,观看他人的体会能有效改善这个问题
1. 目录结构
-
XNode类:
- 作用:
XNode
类表示XML文档中的一个节点(Element或Node),它用于封装XML节点的信息,例如标签名、属性和子节点等。 - 使用场景:MyBatis使用
XNode
来解析XML配置文件中的各种元素,如<select>
,<update>
,<insert>
,<delete>
等,并提供了一种方便的方式来访问这些元素的属性和子元素。
- 作用:
-
PropertyParser类:
- 作用:
PropertyParser
类是MyBatis用于解析属性表达式的工具类。属性表达式是在XML配置文件中引用JavaBean属性的方式,通常用于动态SQL。 - 使用场景:MyBatis在解析动态SQL语句或属性表达式时,会使用
PropertyParser
来解析${}
和#{}
中的属性表达式,并将其替换为实际的属性值或SQL参数。
- 作用:
-
GenericTokenParser类:
- 作用:
GenericTokenParser
类是MyBatis用于解析通用占位符(例如${}
和#{}
)的工具类。它允许你指定一个TokenHandler
接口的实现,用于处理占位符内的内容。 - 使用场景:MyBatis在解析SQL语句中的占位符时,会使用
GenericTokenParser
来查找和替换占位符内的内容,然后将处理后的SQL语句返回。
- 作用:
-
ParsingException类:
- 作用:
ParsingException
类是MyBatis中的自定义异常类,用于表示解析过程中的异常情况。它通常用于捕获和处理解析XML配置文件或SQL语句时可能发生的错误。 - 使用场景:当MyBatis在解析配置文件或SQL语句时遇到不合法的语法或结构时,会抛出
ParsingException
异常,以便开发者识别和处理问题。
- 作用:
-
TokenHandler接口:
- 作用:
TokenHandler
接口是一个回调接口,定义了如何处理占位符内的内容。它用于与GenericTokenParser
类一起工作,允许开发者自定义处理占位符内部内容的逻辑。 - 使用场景:当MyBatis使用
GenericTokenParser
解析占位符时,它会调用TokenHandler
接口的实现来处理占位符内的内容。开发者可以实现自定义的TokenHandler
来定义处理逻辑,例如从配置文件或参数中获取属性值。
- 作用:
-
XPathParser类:
- 作用:
XPathParser
类是MyBatis中的XML解析工具,用于解析XML配置文件和映射文件。它提供了一种方便的方式来处理XML文档,包括节点遍历、属性获取、XPath表达式解析等。 - 使用场景:
XPathParser
类通常用于加载和解析MyBatis的XML配置文件,例如mybatis-config.xml和映射文件。它允许MyBatis以一种结构化的方式访问和操作XML配置文件中的内容。
- 作用:
2. XPathParser类
- 参数
XPathParser
类通常用于加载和解析MyBatis的XML配置文件,例如mybatis-config.xml和映射文件。它允许MyBatis以一种结构化的方式访问和操作XML配置文件中的内容。
public class XPathParser {private final Document document;private boolean validation;private EntityResolver entityResolver;private Properties variables;private XPath xpath;//省略
}
-
document
属性,XML 被解析后,生成的org.w3c.dom.Document
对象。 -
validation
属性,是否校验 XML 。一般情况下,值为true
。 -
entityResolver
属性,org.xml.sax.EntityResolver
对象,XML 实体解析器。默认情况下,对 XML 进行校验时,会基于 XML 文档开始位置指定的 DTD 文件或 XSD 文件。例如说,解析mybatis-config.xml
配置文件时,会加载http://mybatis.org/dtd/mybatis-3-config.dtd
这个 DTD 文件。但是,如果每个应用启动都从网络加载该 DTD 文件,势必在弱网络下体验非常下,甚至说应用部署在无网络的环境下,还会导致下载不下来,那么就会出现 XML 校验失败的情况。所以,在实际场景下,MyBatis 自定义了 EntityResolver 的实现,达到使用本地DTD 文件,从而避免下载网络DTD 文件的效果. -
xpath
属性,javax.xml.xpath.XPath
对象,用于查询 XML 中的节点和元素。如果对 XPath 的使用不了解的胖友,请先跳转 《Java XPath 解析器 - 解析 XML 文档》 中,进行简单学习,灰常简单。 -
variables
属性,变量 Properties 对象,用来替换需要动态配置的属性值。例如:<dataSource type="POOLED"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/> </dataSource>
-
variables
的来源,即可以在常用的 Java 配置文件中配置,也可以使用 MyBatis<property />
标签进行配置。例如:<properties resource="org/mybatis/example/config.properties"><property name="username" value="dev_user"/><property name="password" value="F2Fa3!33TYyg"/> </properties>
- 这里配置的
username
和password
属性,就可以替换上面的${username}
和${password}
这两个动态属性。 - 具体如何实现的,可以看下面的
PropertyParser#parse(String string, Properties variables)
方法。
- 这里配置的
-
- 构造方法
解释完属性后,我们可以看见后面有一长串的构造方法,挑选其中一个
/*** 构造 XPathParser 对象** @param xml XML 文件地址* @param validation 是否校验 XML* @param variables 变量 Properties 对象* @param entityResolver XML 实体解析器*/
public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {//1. 公共构造方法commonConstructor(validation, variables, entityResolver);//2. 创建document对象,将xml文件解析成一个document对象this.document = createDocument(new InputSource(new StringReader(xml)));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {this.validation = validation;this.entityResolver = entityResolver;this.variables = variables;// 创建 XPathFactory 对象XPathFactory factory = XPathFactory.newInstance();this.xpath = factory.newXPath();
}
/*** 创建 Document 对象,这里就是简单的调用别人提供的java xml api,就像我们调用netty提供的api那样** @param inputSource XML 的 InputSource 对象* @return Document 对象*/
private Document createDocument(InputSource inputSource) {// important: this must only be called AFTER common constructortry {// 1. 创建 DocumentBuilderFactory 对象DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();// 设置是否验证 XMLfactory.setValidating(validation); factory.setNamespaceAware(false);factory.setIgnoringComments(true);factory.setIgnoringElementContentWhitespace(false);factory.setCoalescing(false);factory.setExpandEntityReferences(true);// 2. 创建 DocumentBuilder 对象DocumentBuilder builder = factory.newDocumentBuilder();// 设置实体解析器builder.setEntityResolver(entityResolver); // 实现都空的builder.setErrorHandler(new ErrorHandler() { @Overridepublic void error(SAXParseException exception) throws SAXException {throw exception;}@Overridepublic void fatalError(SAXParseException exception) throws SAXException {throw exception;}@Overridepublic void warning(SAXParseException exception) throws SAXException {}});// 3. 解析 XML 文件return builder.parse(inputSource);} catch (Exception e) {throw new BuilderException("Error creating document instance. Cause: " + e, e);}
}
至此,我们可以通过构造方法创建一个对象对xml文件进行解析
例如,在org.apache.ibatis.parsing.XPathParserTest#shouldTestXPathParserMethods
方法中,我们可以通过测试代码解析nodelet_test.xml
中的元素,通过构造方法后获得的XPathParser
对象已经对xml文件进行解析,如下
- eval元素
eval 元素的方法,用于获得 Boolean、Short、Integer、Long、Float、Double、String 类型的元素的值。我们以
#evalString(Object root, String expression)
方法为例子,代码如下:
public String evalString(Object root, String expression) {// 1. 获得指定元素或节点的值String result = (String) evaluate(expression, root, XPathConstants.STRING);// 2. 基于 variables 替换动态值,如果 result 为动态值,还记得variables是什么吗?Properties 对象,用来替换需要动态配置的属性值。result = PropertyParser.parse(result, variables);return result;
}
/*** 获得指定元素或节点的值,例如在上例中箭头所指表达式为"/employee/@id" , 节点为"[#document:null]" , 返回类型为String,那么返回的结果就是result = "${id_var}"** @param expression 表达式* @param root 指定节点* @param returnType 返回类型* @return 值*/
private Object evaluate(String expression, Object root, QName returnType) {try {return xpath.evaluate(expression, root, returnType);} catch (Exception e) {throw new BuilderException("Error evaluating XPath. Cause: " + e, e);}
}
在这里没有使用动态替换,因此即便result = “${id_var}”,在经过PropertyParser#parse(String string, Properties variables)
进行动态解析后,结果依然保持不变.
除了上面的vealString之类的方法,还有一个evalNode()方法,用户获得Node类型的节点的值,代码如下
//返回 Node 数组
public List<XNode> evalNodes(String expression) { return evalNodes(document, expression);
}public List<XNode> evalNodes(Object root, String expression) {// 1. 封装成 XNode 数组List<XNode> xnodes = new ArrayList<>();// 2. 获得 Node 数组NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);for (int i = 0; i < nodes.getLength(); i++) {xnodes.add(new XNode(this, nodes.item(i), variables));}return xnodes;
}//返回 Node 对象
public XNode evalNode(String expression) { return evalNode(document, expression);
}public XNode evalNode(Object root, String expression) {//1. 获得 Node 对象Node node = (Node) evaluate(expression, root, XPathConstants.NODE);if (node == null) {return null;}//2. 封装成 XNode 对象return new XNode(this, node, variables);
}
1处,返回结果有 Node 对象和数组两种情况,根据方法参数 expression
需要获取的节点不同。
2处,会将结果Node封装为org.apache.ibatis.parsing.XNode
对象,主要为了动态值的替换,例如测试方法org.apache.ibatis.parsing.XPathParserTest#shouldTestXPathParserMethods
调用parser.evalNode("/employee/@id").toString().trim()
这条语句时,会产生下面的一个XNode对象,也就是获取xml文件中id节点的值了
这个类基本看完了,今天就到这里了,记录一下
------------------------2023/9/3 未完待续------------------------