Mybatis解析構成ファイルのソースコード分析
TSMYKJavaテクノロジープログラミング
序文
Mybatisを使用したことのある人なら誰でも、Mybatisには、データソース、エイリアス、およびキャッシュの有効化などのグローバル設定を構成するために使用される構成ファイルがあることを知っています。Mybatisが初期化されると、構成ファイルが読み込まれ、構成ファイルは次のようになります。ロードされました。解析;解析にDOMメソッドを使用します。構成ファイル全体をメモリにロードしてツリー構造を形成し、XPathを使用して必要な値を取得します。Mybatisが構成ファイルを解析する方法を見てみましょう。
XPath
ソースコードを見る前に、XPathとは何かを見てみましょう。自動化にPython + seleniumを使用したことがある人は、XPathを使用してボタンなどのページ要素を見つけ、手動でページをクリックする代わりにイベントを追加することを知っておく必要があります。 。簡単に言うと、XPathはXML要素を見つけるために使用され、XMLドキュメント内の要素と属性をトラバースするために使用できます。XPathには独自の構文があります。詳細については、XPathチュートリアルを参照してください。
W3School、XPathチュートリアル(http://www.w3school.com.cn/xpath/index.ASP)
構成ファイル
Mybatisの設定ファイルはおおまかに次のとおりであり、ソースコード分析ではそれを使用して分析します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 启动默认值 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias type="mybatis.pojo.Person" alias="person"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username:root}"/><!-- 使用默认值 -->
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis-mapper.xml"/>
</mappers>
</configuration>
ソースコード分析
Mybatis解析構成ファイルのクラスは、主に解析パッケージの下にあります。以下に示すように、このパッケージの下には6つのクラスしかありません。
XPathParser:これはJavaによって提供されるXPathクラスのラッパーであり、メインロジックはこのクラスに実装されています。
PropertyParser:プロパティパーサー
TokenHandler:プレースホルダーパーサーはインターフェイスであり、サブクラスはそれ自体で解析ルールを実装します
。GenericTokenParser:一般的なプレースホルダーパーサー。#{}および$ {}パラメーターの処理に使用されます
。XNode:ドキュメントをロードします。 labelはノードであり、ノード、親ノード、子ノードなどの属性を取得できます。
ParsingExceptionは、Javaノードクラスのラッパーです。カスタム例外は無視できます
。上記のクラス間の関係は、おおよそ次のとおりです。
最初にこれらのクラスのソースコードを見て、最後にこの図を見てみましょう。
XPathParser
最初にXPathParserクラスを見てみましょう。これは主に構成ファイルをロードし、ファイル内のノード値を取得するためのエントリを提供するために使用されます。
// 该类共有 5 个属性
public class XPathParser {
// 需要解析的文档
private Document document;
// 是否开启验证,即加载对应的DTD文件或XSD文件进行验证,如果开启的话,会联网加载,否则的话会加载本地的DTD文件进行验证
private boolean validation;
// 用于加载本地的 DTD 文件,可以忽略不看
private EntityResolver entityResolver;
// 对应 mybatis-config 配置文件中 <properties> 标签
private Properties variables;
// XPath 对象
private XPath xpath;
// XPathParser 提供了很多重载的构造方法,这里就不一一列出来了
public XPathParser(InputStream inputStream) {
// 设置上面 4 个属性
commonConstructor(false, null, null);
// 为 document 属性赋值
this.document = createDocument(new InputSource(inputStream));
}
// 构造方法调用,用于为属性赋值
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();
}
// 根据输入流来创建文档,返回代表该文档的一个 Document 对象
private Document createDocument(InputSource inputSource) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// ..........忽略........
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
// ..........忽略........
});
// 通过 DocumentBuilder 解析文档,返回代表该文档的一个 Document 对象
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
}
コードの上記の部分を渡した後、ストリームを介して構成ファイルを読み取り、ドキュメントを表すDocumentオブジェクトを作成できます。次に、ファイル内の値を取得する場合は、次の方法でXPath式を実行します。のXPathオブジェクト、
このクラスには多くのeval *()メソッドがあり、対応する型の値を取得するために使用されますが、最終的にはXPathオブジェクトのevaluate()を呼び出して取得します。evalString()を代表として見てください。取得方法:
// 执行 XPath 表达式
public String evalString(String expression) {
return evalString(document, expression);
}
// 在 root 上执行 XPath 表达式
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
// 根据表达式,文档对象,和返回类型,调用 XPath 对象的 evaluate 方法执行表达式
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);
}
}
// 文档的返回类型
public class XPathConstants {
// 数值类型
public static final QName NUMBER = new QName("http://www.w3.org/1999/XSL/Transform", "NUMBER");
// String 类型
public static final QName STRING = new QName("http://www.w3.org/1999/XSL/Transform", "STRING");
// boolean 类型
public static final QName BOOLEAN = new QName("http://www.w3.org/1999/XSL/Transform", "BOOLEAN");
// NodeList 类型
public static final QName NODESET = new QName("http://www.w3.org/1999/XSL/Transform", "NODESET");
// Node 类型
public static final QName NODE = new QName("http://www.w3.org/1999/XSL/Transform", "NODE");
// 不知道啥类型
// The URI for the DOM object model
public static final String DOM_OBJECT_MODEL = "http://java.sun.com/jaxp/xpath/dom";
}
上記のevalStringメソッドでは、XPathを呼び出して実行した後、PropertyParserのparseメソッドを呼び出して結果を解析します。このメソッドは、ノード内の対応するデフォルト値を処理するために使用されます。このメソッドはここでは説明しません。後でそれで。
対応するタイプの値を取得することに加えて、対応するノード、つまりXNodeまたはXNodeのコレクションを返すこともできます。
// 根据表达式获取 XNode 集合
public List<XNode> evalNodes(String expression) {
return evalNodes(document, expression);
}
public List<XNode> evalNodes(Object root, String expression) {
List<XNode> xnodes = new ArrayList<XNode>();
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;
}
// 获取单个 XNode
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
上記はXPathParserクラスのメインコードであり、まだよく理解されています。evalStringメソッドのPropertyParser.parseに特別な注意を払う必要があります。次に、このクラスの実装を見てみましょう。
PropertyParser
PropertyParserプロパティパーサーには2つの主要な機能があります。1つはデフォルト値が有効かどうかを判断すること、もう1つはデフォルト値が有効かどうかを判断することです。キーに従って値を取得できない場合は、デフォルト値が使用されます。
ソースコードは次のとおりです。
public class PropertyParser {
// 是否开启默认值的前缀
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
// 开启默认值的属性
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 = ":";
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 先忽略
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
// 占位符的一个实现类,
private static class VariableTokenHandler implements TokenHandler {
// 属性值,相当于一个 map,里面存放着属性和值的对应关系
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);
}
// 根据key在 variables 中获取对应的值
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
// 主要方法
// 该方法会在 GenericTokenParser.parse() 方法中进行回调
// 当从 GenericTokenParser 中解析得到属性名的时候,会把属性名传入该方法来去 variables 中查找对应的值,如果找不到且开启了默认值,则返回默认值
@Override
public String handleToken(String content) {
// 如果属性集合不为空
if (variables != null) {
// 属性名
String key = content;
// 是否开启默认值
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
// 从属性名+分隔符+默认值(name:defaultVal)的字符串中获取属性名
key = content.substring(0, separatorIndex);
// 获取默认值
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
// 有默认值
if (defaultValue != null) {
// 在 属性集合中获取对应的属性值,如果不存在,则返回默认值
return variables.getProperty(key, defaultValue);
}
}
// 如果还没开启默认值,则直接中属性集合中获取,获取不到返回null
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 如果属性集合为空,则直接返回 ${name} 的形式
return "${" + content + "}";
}
}
}
このクラスには、主に2つのメソッドがあります。
-
parse()メソッド。このメソッドでは、GenericTokenParserのparse()メソッドが解析のために呼び出されます。ここでは気にしないでください。$ {name}の形式の文字列から名前文字列を取得することを知っておいてください。 。
- 内部クラスであるVariableTokenHandlerクラスのhandleToken()メソッドは、TokenHandlerインターフェイスを実装します。GenericTokenParserのparse()メソッドから属性名を取得すると、属性名を使用して、で対応する値が検索されます。属性コレクション。デフォルト値が見つからず有効になっている場合は、デフォルト値が返され、GenericTokenParserのparse()メソッドでhandleToken()メソッドがコールバックされます。
このクラスは、主に属性名に基づいて、属性コレクションから値を取得します。次に、GenericTokenParserクラスを見てください
GenericTokenParser
このクラスのオブジェクトは、上記のPropertyParserクラスのparse()メソッドで作成され、プレースホルダープロセッサVariableTokenHandlerが渡されます。
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
GenericTokenParserクラスは、#{}や$ {}などのプレースホルダーの解析などの一般的なプレースホルダーパーサーです。そのparse()メソッドは、プレースホルダーの開始タグと終了タグを順番に検索し、リテラル値を解析して取得したパーサーを検索します。プレースホルダーのを取得し、それをプレースホルダープロセッサVariableTokenHandlerに渡して処理します。つまり、handleToken()メソッドを実行します。
次に、このクラスのソースコードを見てください。
public class GenericTokenParser {
// 占位符的开始标记
private final String openToken;
// 占位符的结束标记
private final String closeToken;
// 占位符处理器
private final TokenHandler handler;
// 构造
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
// 解析 ${name} 之类的字符串
public String parse(String text) {
// 省略.........
final StringBuilder builder = new StringBuilder();
// 调用占位符的 handleToken 方法处理
builder.append(handler.handleToken(expression.toString()));
// 省略.........
return builder.toString();
}
}
構成ファイルが次の場合:
<properties>
<property name="username" value="root"/>
<property name="password" value="root"/>
<!-- 启动默认值 -->
<property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
</properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username:root}"/><!-- 使用默认值 -->
<property name="password" value="${password}"/>
</dataSource>
ここでは、PropertyParserの解析メソッドを見てみましょう。
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
次に、属性コレクションでユーザー名に対応する値を取得する必要があります。値が見つからない場合は、デフォルト値のルートが返されます。
この時点で、構成ファイルを解析するロジックのほとんどが完了し、ドキュメント内のノードを表すXNodeクラスがあります。これは、JavaのNodeクラスのラッパーであるため、無視できます。 、ノードのプロパティを取得できる、子ノード、親ノードなど。
くるみ
最初に栗をあげました。それでは、そのプロセスの1つを見てみましょう。UML図は次のとおりです。
ドキュメントがXPathParserクラスにロードされてDocumentオブジェクトが形成されると、プロパティの値を取得するには、まずXPathを介してプロパティ値を取得し、次にPropertyParserのparse()メソッドを介して値を取得します。このメソッドでは、 GenericToenParserを渡しますプレースホルダーを解析した後、リテラル文字列属性を取得し、VariableTokenHandlerのhandleTokenメソッドを使用して属性コレクションで対応する値を見つけます。対応する値が見つからず、デフォルト値が有効になっている場合、デフォルト値はが返され、最後に属性に対応する値を取得します。これは、ドキュメント内の対応する値を取得するプロセスです。
上記は、Mybatisが構成ファイルを解析するためのツールです。