Mybatis 구문 분석 구성 파일의 소스 코드 분석

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 개의 클래스 만 있습니다.
Mybatis 구문 분석 구성 파일의 소스 코드 분석

XPathParser : Java에서 제공하는 XPath 클래스의 래퍼이며이 클래스에서 주요 논리가 구현됩니다.
PropertyParser : Property Parser
TokenHandler : Placeholder Parser는 인터페이스이며 하위 클래스는 자체적으로 구문 분석 규칙을 구현합니다
GenericTokenParser : # {} 및 $ {} 매개 변수를 처리하는 데 사용되는 일반 자리 표시 자 구문 분석기
XNode : 문서로드 메모리에 도달 한 후 각각 label은 노드, 부모 노드, 자식 노드 등의 속성을 얻을 수있는 노드입니다.
ParsingException은 Java Node 클래스의 래퍼입니다 : 사용자 정의 예외는 무시할 수 있습니다
. 위 클래스 간의 관계는 대략 다음과 같습니다.
Mybatis 구문 분석 구성 파일의 소스 코드 분석

먼저 이러한 클래스의 소스 코드를 살펴보고 마지막으로이 그림을 살펴 보겠습니다.

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 + "}";
    }
  }
}

이 클래스에는 주로 두 가지 방법이 있습니다.

  1. parse () 메서드.이 메서드에서는 GenericTokenParser의 parse () 메서드가 구문 분석을 위해 호출됩니다. 여기서는 걱정하지 마십시오. $ {name} 형식의 문자열에서 이름 문자열을 가져옵니다. .

  2. 내부 클래스 인 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 다이어그램은 다음과 같습니다.
Mybatis 구문 분석 구성 파일의 소스 코드 분석

문서가 XPathParser 클래스에로드되어 Document 객체를 형성 할 때 이제 속성 값을 가져 오려면 먼저 XPath를 통해 속성 값을 가져온 다음이 메서드에서 PropertyParser의 parse () 메서드를 통해 값을 가져옵니다. GenericToenParser를 전달합니다 자리 표시자를 파싱 한 후 리터럴 문자열 속성을 가져온 다음 VariableTokenHandler의 handleToken 메소드를 통해 속성 컬렉션에서 해당 값을 찾습니다. 해당 값이없고 기본값이 활성화 된 경우 기본값이됩니다. 반환되고 마지막으로 속성에 해당하는 값을 가져옵니다. 이것은 문서에서 해당 값을 가져 오는 프로세스입니다.

위는 Mybatis가 구성 파일을 구문 분석하는 도구입니다.

추천

출처blog.51cto.com/15077536/2608561