Mybatis 구문 분석 구성 파일의 소스 코드 분석
TSMYK Java 기술 프로그래밍
머리말
Mybatis를 사용한 사람은 누구나 Mybatis에 데이터 소스, 별칭 및 캐시 켜기와 같은 일부 전역 설정을 구성하는 데 사용되는 구성 파일이 있음을 알고 있습니다. Mybatis가 초기화되면 구성 파일이로드되고 구성 파일이 파싱, 파싱을 위해 DOM 메소드를 사용하며 전체 구성 파일을 메모리에로드하여 트리 구조를 형성 한 다음 XPath를 사용하여 필요한 값을 가져옵니다. Mybatis가 구성 파일을 구문 분석하는 방법을 살펴 보겠습니다.
XPath
소스 코드를보기 전에 XPath가 무엇인지 살펴 보겠습니다. 자동화를 위해 Python + 셀레늄을 사용한 사람이라면 누구나 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 : Property Parser
TokenHandler : Placeholder Parser는 인터페이스이며 하위 클래스는 자체적으로 구문 분석 규칙을 구현합니다
GenericTokenParser : # {} 및 $ {} 매개 변수를 처리하는 데 사용되는 일반 자리 표시 자 구문 분석기
XNode : 문서로드 메모리에 도달 한 후 각각 label은 노드, 부모 노드, 자식 노드 등의 속성을 얻을 수있는 노드입니다.
ParsingException은 Java Node 클래스의 래퍼입니다 : 사용자 정의 예외는 무시할 수 있습니다
. 위 클래스 간의 관계는 대략 다음과 같습니다.
먼저 이러한 클래스의 소스 코드를 살펴보고 마지막으로이 그림을 살펴 보겠습니다.
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 객체의 evaluation ()을 호출하여이를 가져옵니다. 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 속성 파서는 두 가지 주요 기능이 있는데, 하나는 기본값 사용 여부를 확인하는 기능이고 다른 하나는 기본값 사용 여부를 확인하는 기능으로 키에 따라 값을 가져올 수없는 경우 기본값을 사용합니다.
소스 코드는 다음과 같습니다.
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 + "}";
}
}
}
이 클래스에는 주로 두 가지 방법이 있습니다.
-
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);
}
이제 속성 컬렉션에서 username에 해당하는 값을 가져와야합니다. 찾을 수없는 경우 기본값 root를 반환합니다.
이 시점에서 구성 파일을 구문 분석하는 대부분의 논리가 완료되었으며 이제 문서의 노드를 나타내는 XNode 클래스가 있습니다.이 클래스는 무시할 수 있습니다. Java의 Node 클래스에 대한 래퍼임을 알 수 있습니다. , 노드의 속성을 얻을 수있는, 자식 노드, 부모 노드 등.
밤나무
처음에는 밤나무를 주었는데 이제 그 과정 중 하나를 살펴 보겠습니다. UML 다이어그램은 다음과 같습니다.
문서가 XPathParser 클래스에로드되어 Document 객체를 형성 할 때 이제 속성 값을 가져 오려면 먼저 XPath를 통해 속성 값을 가져온 다음이 메서드에서 PropertyParser의 parse () 메서드를 통해 값을 가져옵니다. GenericToenParser를 전달합니다 자리 표시자를 파싱 한 후 리터럴 문자열 속성을 가져온 다음 VariableTokenHandler의 handleToken 메소드를 통해 속성 컬렉션에서 해당 값을 찾습니다. 해당 값이없고 기본값이 활성화 된 경우 기본값이됩니다. 반환되고 마지막으로 속성에 해당하는 값을 가져옵니다. 이것은 문서에서 해당 값을 가져 오는 프로세스입니다.
위는 Mybatis가 구성 파일을 구문 분석하는 도구입니다.