在MyBatis中涉及多個xml文件,解析這些xml文件天然離不開解析器。本文就來分析一下解析器模塊。java
xml常見的解析方式分爲如下三種:node
詳細的解析xml學習能夠參考 Java解析XML 在這裏咱們須要重點看下DOM解析,DOM解析主要的好處就是易於編程,能夠跟根據需求在樹形結構的各個節點之間導航。express
MyBatis 在初始化過程當中處理mybatis-config.xml
以及映射文件時使用的是DOM解析方式,並結合使用XPath解析XML配置文件。DOM會將整個XML文檔加載到內存中造成數據結構。apache
XPathParser類封裝了XPath 、Document和EntityResolver 依賴關係如圖所示編程
XPathParser中字段含義和功能以下微信
private final Document document; //Document 對象 private boolean validation; //是否開啓校驗 private EntityResolver entityResolver; //用於加載本地DTD文件 private Properties variables; //mybatis-config.xml <properties> 標籤訂義的鍵值對集合 private XPath xpath; //XPath對象
mybatis-config.xml
文件時,默認聯網加載http://mybatis.org/dtd/mybatis-3-config.dtd
這個DTD文檔,當網絡比較慢會使加載變緩慢。其實在MyBatis中已經配置了關於DTD文件的映射關係。XMLMapperEntityResolver
中實現了EntityResolver接口,並配置加載本地的DTD文件。關係如圖所示:EntityResolver接口的核心是resolveEntity() 方法,XMLMapperEntityResolver的實現以下:網絡
public class XMLMapperEntityResolver implements EntityResolver { // 指定mybatis-config.xml 文件和映射文件對應的dtd的SystemId private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd"; private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd"; private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd"; private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd"; // 指定指定mybatis-config.xml 文件和映射文件對應的dtd的具體位置 private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd"; private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd"; /** * Converts a public DTD into a local one. * * @param publicId The public id that is what comes after "PUBLIC" * @param systemId The system id that is what comes after the public id. * @return The InputSource for the DTD * * @throws org.xml.sax.SAXException If anything goes wrong */ //實現EntityResolver接口的resolveEntity方法 @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException { try { if (systemId != null) { String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH); //查找systemId指定的dtd文件,並調用getInputSource發放讀取dtd文檔 if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) { return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId); } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) { return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId); } } return null; } catch (Exception e) { throw new SAXException(e.toString()); } } // getInputSource()方法負責讀取DTD文件造成InputSource對象 private InputSource getInputSource(String path, String publicId, String systemId) { InputSource source = null; if (path != null) { try { InputStream in = Resources.getResourceAsStream(path); source = new InputSource(in); source.setPublicId(publicId); source.setSystemId(systemId); } catch (IOException e) { // ignore, null is ok } } return source; } }
介紹完XMLMapperEntityResolver
以後,咱們回到XPathParser這個類上接下來咱們按照組成部分挨個拆分出來。數據結構
XPathParser構造方法有16種,應該是知足各類各樣不一樣使用場景下的需求吧。mybatis
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(); } //調用createDocument發放以前必定要先調用 commonConstructor() 方法完成初始化 private Document createDocument(InputSource inputSource) { // important: this must only be called AFTER common constructor try { // 建立 DocumentBuilderFactory 對象 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //對 DocumentBuilderFactory 對象一系列配置 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); factory.setValidating(validation); factory.setNamespaceAware(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(false); factory.setCoalescing(false); factory.setExpandEntityReferences(true); //建立 DocumentBuilder 對象並進行配置 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 { // NOP } }); //加載 xml 文件 return builder.parse(inputSource); } catch (Exception e) { throw new BuilderException("Error creating document instance. Cause: " + e, e); } }
XPathParser.createDocument()
方法中封裝了建立Document
對象的過程並觸發了加載XML文檔的過程。XPathParser
中提供了一系列的eval*()方法用於解析boolean、short、Integer、Long、Float、Sting、Double、Node等類型的信息。XPath.evaluate()
方法查找指定路徑的節點霍屬性,並進行相應的類型轉換。XPathParser.evalString()
方法會調用PropertyParser.parse()
方法處理節點中相應的默認值,具體實現代碼以下:public String evalString(Object root, String expression) { String result = (String) evaluate(expression, root, XPathConstants.STRING); result = PropertyParser.parse(result, variables); return result; }
PropertyParser```中指定了是否開啓使用默認值的功能以及默認的分隔符,相關代碼以下:app
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser."; //在 mybatis-config.xml 中<properties>節點下配置是否開啓默認值功能的對應配置項 public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value"; //配置佔位符與默認值之間的默認分隔符的對應配置項 public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator"; //默認狀況下關閉默認值的功能 private static final String ENABLE_DEFAULT_VALUE = "false"; //默認分隔符是冒號 private static final String DEFAULT_VALUE_SEPARATOR = ":"; private PropertyParser() { // Prevent Instantiation } public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); //建立 GenericTokenParser 解析器對象 並制定其佔位符爲 ${} GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); }
方法建立
GenericTokenParser解析器,並將默認值的處理委託給
GenericTokenParser.parse()```方法。GenericTokenParser是通用的佔位符解析器,具體代碼以下:
package org.apache.ibatis.parsing; /** * 通用的佔位符解析器 * @author Clinton Begin */ public class GenericTokenParser { private final String openToken; //佔位符的開始標記 private final String closeToken; //佔位符的結束標記 private final TokenHandler handler; //TokenHandler接口的實現會按照必定的邏輯解析佔位符 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } public String parse(String text) { //檢測 text 是否爲空 if (text == null || text.isEmpty()) { return ""; } // search open token // 查找開始標記 int start = text.indexOf(openToken); if (start == -1) { return text; } char[] src = text.toCharArray(); int offset = 0; //用來標記解析後的字符串 final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // this open token is escaped. remove the backslash and continue. // 遇到轉義的開始標記 則直接將前面的字符串以及開始標記追加到builder中 builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //查找到開始標記,且未轉義 // found open token. let's search close token. if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } //將前面的字符串追加到builder中 builder.append(src, offset, start - offset); //修改offset位置 offset = start + openToken.length(); // 從offset後繼續查找結束標記 int end = text.indexOf(closeToken, offset); while (end > -1) { if (end > offset && src[end - 1] == '\\') { //處理轉義的結束標記 // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else { //將開始標記和結束標記之間的字符串追加到expression中保存 expression.append(src, offset, end - offset); break; } } if (end == -1) { //未找到結束標記 // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { //將佔位符的字面值交給TokenHandler處理,並將處理結果追加到builder中保存 builder.append(handler.handleToken(expression.toString())); //最終拼湊出解析後完整的內容 offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); //移動start } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } }
GenericTokenParser.parse()
方法比較簡單,具體實現就是順序查找openToken
和closeToken
,解析獲得佔位符的字面值Tokenhandler
處理,而後將解析結果從新拼裝成字符串返回。<img src="http://qiniu-cdn.janker.top/oneblog/20200105225624602.png" style="zoom:67%;" /> ## 6. PropertyParser PropertyParser是使用VariableTokenHandler和GenericTokenParser配合完成佔位符解析。代碼以下: ------
package org.apache.ibatis.parsing;
import java.util.Properties;
/**
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
//在 mybatis-config.xml 中
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
//配置佔位符與默認值之間的默認分隔符的對應配置項
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
//默認狀況下關閉默認值的功能
private static final String ENABLE_DEFAULT_VALUE = "false";
//默認分隔符是冒號
private static final String DEFAULT_VALUE_SEPARATOR = ":";
private PropertyParser() {
// Prevent Instantiation
}
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
//建立 GenericTokenParser 解析器對象 並制定其佔位符爲 ${}
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
private final boolean enableDefaultValue;
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) { this.variables = variables; this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE)); this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR); } private String getPropertyValue(String key, String defaultValue) { return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue); } @Override public String handleToken(String content) { // 檢測 variables 集合是否爲空 if (variables != null) { String key = content; //檢測是否支持佔位符中使用默認值的功能 if (enableDefaultValue) { // 查找分隔符 final int separatorIndex = content.indexOf(defaultValueSeparator); String defaultValue = null; if (separatorIndex >= 0) { // 獲取佔位符的名稱 key = content.substring(0, separatorIndex); //獲取默認值 defaultValue = content.substring(separatorIndex + defaultValueSeparator.length()); } if (defaultValue != null) { //在variable集合中查找指定佔位符 return variables.getProperty(key, defaultValue); } } // 不支持默認值的功能,直接查找variables集合 if (variables.containsKey(key)) { return variables.getProperty(key); } } return "${" + content + "}"; //variables集合爲空 直接返回 }
}
}
------ - VariableTokenHandler```是```PropertyParser```類中的一個靜態內部類。 - ```VariableTokenHandler```實現了```TokenHandler```接口中的```handlerToken()```方法 - 該實現首先按照defaultValueSeparator字段指定的分隔符對整個佔位符進行切分,獲得佔位符的名稱和默認值,而後按照切分獲得的佔位符名稱查找對應的值 - 若是在```<properties>```節點下未定義相應的鍵值對,則將切分獲得額默認值做爲解析結果返回。 ## 7. XNode XPathParser.evalNode()方法返回的類型爲XNode,他對org.w3c.dom.Node對象驚醒了封裝和解析,具體代碼以下: ------
public class XNode {
private final Node node; //org.w3c.dom.Node對象
private final String name; //Node節點名稱
private final String body; //節點內容
private final Properties attributes; //節點屬性集合
private final Properties variables; //mybatis-config.xml配置文件中
private final XPathParser xpathParser; //xpathParser對象 xNode由XPathParser對象生成
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName();
this.variables = variables;
this.attributes = parseAttributes(node);
this.body = parseBody(node);
}
public XNode newXNode(Node node) {
return new XNode(xpathParser, node, variables);
}
public XNode getParent() {
Node parent = node.getParentNode();
if (!(parent instanceof Element)) {
return null;
} else {
return new XNode(xpathParser, parent, variables);
}
}
public String getPath() {
StringBuilder builder = new StringBuilder();
Node current = node;
while (current instanceof Element) {
if (current != node) {
builder.insert(0, "/");
}
builder.insert(0, current.getNodeName());
current = current.getParentNode();
}
return builder.toString();
}
public String getValueBasedIdentifier() {
StringBuilder builder = new StringBuilder();
XNode current = this;
while (current != null) {
if (current != this) {
builder.insert(0, "");
}
String value = current.getStringAttribute("id",
current.getStringAttribute("value",
current.getStringAttribute("property", null)));
if (value != null) {
value = value.replace('.', '');
builder.insert(0, "]");
builder.insert(0,
value);
builder.insert(0, "[");
}
builder.insert(0, current.getName());
current = current.getParent();
}
return builder.toString();
}
public String evalString(String expression) {
return xpathParser.evalString(node, expression);
}
public Boolean evalBoolean(String expression) {
return xpathParser.evalBoolean(node, expression);
}
public Double evalDouble(String expression) {
return xpathParser.evalDouble(node, expression);
}
public List
return xpathParser.evalNodes(node, expression);
}
public XNode evalNode(String expression) {
return xpathParser.evalNode(node, expression);
}
public Node getNode() {
return node;
}
public String getName() {
return name;
}
public String getStringBody() {
return getStringBody(null);
}
public String getStringBody(String def) {
if (body == null) {
return def;
} else {
return body;
}
}
public Boolean getBooleanBody() {
return getBooleanBody(null);
}
public Boolean getBooleanBody(Boolean def) {
if (body == null) {
return def;
} else {
return Boolean.valueOf(body);
}
}
public Integer getIntBody() {
return getIntBody(null);
}
public Integer getIntBody(Integer def) {
if (body == null) {
return def;
} else {
return Integer.parseInt(body);
}
}
public Long getLongBody() {
return getLongBody(null);
}
public Long getLongBody(Long def) {
if (body == null) {
return def;
} else {
return Long.parseLong(body);
}
}
public Double getDoubleBody() {
return getDoubleBody(null);
}
public Double getDoubleBody(Double def) {
if (body == null) {
return def;
} else {
return Double.parseDouble(body);
}
}
public Float getFloatBody() {
return getFloatBody(null);
}
public Float getFloatBody(Float def) {
if (body == null) {
return def;
} else {
return Float.parseFloat(body);
}
}
public <T extends Enum
return getEnumAttribute(enumType, name, null);
}
public <T extends Enum
String value = getStringAttribute(name);
if (value == null) {
return def;
} else {
return Enum.valueOf(enumType, value);
}
}
// ** 省略 get*()方法
@Override
public String toString() {
StringBuilder builder = new StringBuilder(); toString(builder, 0); return builder.toString();
}
private void toString(StringBuilder builder, int level) {
// ** 省略 toString **
}
private void indent(StringBuilder builder, int level) {
for (int i = 0; i < level; i++) {
builder.append(" ");
}
}
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
// 獲取節點的屬性集合
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
//使用PropertyParser處理每一個屬性中的佔位符
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
private String parseBody(Node node) {
String data = getBodyData(node);
if (data == null) { //當前節點不是文本節點
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
//處理子節點
Node child = children.item(i);
data = getBodyData(child);
if (data != null) {
break;
}
}
}
return data;
}
private String getBodyData(Node child) {
if (child.getNodeType() == Node.CDATA_SECTION_NODE
|| child.getNodeType() == Node.TEXT_NODE) { //只處理文本內容
String data = ((CharacterData) child).getData();
// 使用 PropertyParser處理文本節點中的佔位符
data = PropertyParser.parse(data, variables);
return data;
}
return null;
}
}
```
的構造函數會調用其
parseAttributes() 方法和
parseBody()方法解析
org.w3c.dom.Node對象中的信息,初始化
attributes集合和
body```字段。XNode
中提供了多種get*()
方法獲取所需的節點信息,這些信息主要描述attributes
集合、body
字段、node
字段。XNode.node
。以上就是MyBatis
的解析器模塊的所有內容,下一篇博客咱們繼續分析反射模塊。
本文由 Janker 創做,採用 CC BY 3.0 CN協議 進行許可。 可自由轉載、引用,但需署名做者且註明文章出處。如轉載至微信公衆號,請在文末添加做者公衆號二維碼。