Análisis de código fuente del archivo de configuración de análisis de Mybatis

Análisis de código fuente del archivo de configuración de análisis de Mybatis

Programación de tecnología Java TSMYK

Prefacio

Cualquiera que haya usado Mybatis sabe que Mybatis tiene un archivo de configuración que se usa para configurar fuentes de datos, alias y algunas configuraciones globales como encender la caché. Cuando se inicializa Mybatis, el archivo de configuración se cargará y el archivo de configuración será Cargado. Analizando; usa el método DOM para analizar. Carga todo el archivo de configuración en la memoria para formar una estructura de árbol, y luego usa XPath para obtener el valor que necesitamos de él. Echemos un vistazo a cómo Mybatis analiza el archivo de configuración.

XPath

Antes de mirar el código fuente, echemos un vistazo a lo que es XPath. Cualquiera que haya usado Python + selenium para la automatización debe saber que usa XPath para ubicar elementos de página, como botones, y luego agregar eventos en lugar de hacer clic manualmente en la página . En pocas palabras, XPath se utiliza para localizar elementos XML y se puede utilizar para recorrer elementos y atributos en documentos XML. XPath tiene su propia sintaxis, consulte el tutorial de XPath para obtener más detalles

W3School, tutorial de XPath ( http://www.w3school.com.cn/xpath/index.ASP )

Archivo de configuración

El archivo de configuración de Mybatis es aproximadamente el siguiente, y el análisis del código fuente lo usa para analizar:


<?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>

Análisis de código fuente

Las clases de archivos de configuración de análisis de Mybatis se encuentran principalmente en el paquete de análisis. Solo hay 6 clases en este paquete, como se muestra a continuación:
Análisis de código fuente del archivo de configuración de análisis de Mybatis

XPathParser: es un contenedor de la clase XPath proporcionada por Java, y la lógica principal se implementa en esta clase.
PropertyParser: Property Parser
TokenHandler: Placeholder Parser, es una interfaz, las subclases implementan las reglas de análisis por sí mismas.
GenericTokenParser: Analizador de marcador de posición general, utilizado para procesar los parámetros # {} y $ {}
XNode: Cargar el documento Después de alcanzar la memoria, cada etiqueta es un nodo, a través del cual se pueden obtener los atributos del nodo, nodo padre, nodo hijo, etc.
ParsingException es un contenedor para la clase Java Node : las excepciones personalizadas se pueden ignorar
. La relación entre las clases anteriores es aproximadamente la siguiente:
Análisis de código fuente del archivo de configuración de análisis de Mybatis

Primero echemos un vistazo al código fuente de estas clases y, finalmente, veamos esta imagen.

XPathParser

Veamos primero la clase XPathParser, que se utiliza principalmente para cargar archivos de configuración y proporcionar una entrada para obtener valores de nodo en el archivo.


// 该类共有 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);
    }
  }
}

Después de pasar la parte anterior del código, puede leer el archivo de configuración a través de la secuencia para crear un objeto Documento que represente el documento; a continuación, si desea obtener el valor en el archivo, lo que debe hacer es ejecutar la expresión XPath a través de el objeto XPath de,

Hay muchos métodos eval * () en esta clase, que se utilizan para obtener el valor del tipo correspondiente, pero eventualmente llamarán a la evaluación () del objeto XPath para obtenerlo. Tome evalString () como representante para ver cómo se obtiene:


  // 执行 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";
}

En el método evalString anterior, después de llamar a XPath para que se ejecute, llame al método de análisis de PropertyParser para analizar el resultado. Este método se utiliza para procesar el valor predeterminado correspondiente en el nodo. Este método no se verá aquí, y veremos en eso más tarde.

Además de obtener el valor del tipo correspondiente, también puede devolver el nodo correspondiente, es decir, una colección de XNode o 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);
  }

El anterior es el código principal de la clase XPathParser, que todavía se entiende bien. Se debe prestar especial atención a PropertyParser.parse en el método evalString. A continuación, veamos una implementación de esta clase.

PropertyParser

PropertyParser El analizador de propiedades tiene dos funciones principales: una es determinar si el valor predeterminado está habilitado y la otra es determinar si el valor predeterminado está habilitado.Cuando el valor no se puede obtener de acuerdo con la clave, se utiliza el valor predeterminado.

El código fuente es el siguiente:


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

En esta clase, hay principalmente dos métodos:

  1. El método parse (). En este método, se llamará al método parse () de GenericTokenParser para el análisis. No se preocupe, solo sepa que obtiene la cadena de nombre de una cadena en la forma de $ {nombre} .

  2. El método handleToken () de la clase VariableTokenHandler, que es una clase interna de la misma, implementa la interfaz TokenHandler. Cuando el nombre del atributo se obtiene del método parse () de GenericTokenParser, el nombre del atributo se utilizará para encontrar el valor correspondiente en Si el valor predeterminado no se encuentra y no se habilita, se devolverá el valor predeterminado y se volverá a llamar al método handleToken () en el método parse () de GenericTokenParser.

Esta clase se basa principalmente en el nombre del atributo para obtener el valor de la colección de atributos. A continuación, observe la clase GenericTokenParser

GenericTokenParser

Se crea un objeto de esta clase en el método parse () de la clase PropertyParser mencionada anteriormente, y se pasa un procesador de marcador de posición VariableTokenHandler


GenericTokenParser parser = new GenericTokenParser("${", "}", handler);

La clase GenericTokenParser es un analizador de marcador de posición general, como el análisis de marcadores de posición como # {} y $ {}. Su método parse () buscará las etiquetas de inicio y finalización del marcador de posición en orden y el analizador obtenido analizando el valor literal del marcador de posición y luego entréguelo al procesador de marcador de posición VariableTokenHandler para su procesamiento, es decir, ejecute el método handleToken ()

A continuación, mire el código fuente de esta clase:


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();
  }
}

Si el archivo de configuración es el siguiente:


<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>

Aquí, echemos un vistazo al método de análisis de PropertyParser:


  public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

Ahora necesitamos obtener el valor correspondiente al nombre de usuario en la colección de atributos, si no puede encontrarlo, devolverá el valor predeterminado root.

En este punto, la mayor parte de la lógica del análisis del archivo de configuración se ha completado, y ahora hay una clase XNode, que representa el nodo en el documento, que se puede ignorar, sabiendo que es un contenedor para la clase Node de Java. , a través del cual se pueden obtener las propiedades del nodo, nodos secundarios, nodos principales, etc.

castaña

Di una castaña al principio. Ahora veamos uno de sus procesos. El diagrama UML es el siguiente:
Análisis de código fuente del archivo de configuración de análisis de Mybatis

Cuando el documento se carga en la clase XPathParser para formar un objeto Document, ahora para obtener el valor de una propiedad, primero obtenga el valor de la propiedad a través de XPath y luego obtenga el valor a través del método parse () de PropertyParser, en este método, pasará GenericToenParser Después de analizar el marcador de posición, obtenga un atributo de cadena literal y luego busque el valor correspondiente en la colección de atributos a través del método handleToken de VariableTokenHandler. Si no se encuentra el valor correspondiente y el valor predeterminado está habilitado, el valor predeterminado será ser devuelto, y finalmente obtener el valor correspondiente al atributo. Este es un proceso para obtener el valor correspondiente en el documento.

La anterior es la herramienta de Mybatis para analizar los archivos de configuración.

Supongo que te gusta

Origin blog.51cto.com/15077536/2608561
Recomendado
Clasificación