解析器模块

解析器模块的功能主要是将mybatis的配置文件和映射文件解析成DOM对象,供后续执行核心处理逻辑使用。

一、XML的解析方式:

DOM(Document Object Model):基于树形结构的解析方式,会将整个xml文档读入内存并解析成DOM树。再对树结构进行解析,优点是可以方便的操作树结构的每一个节点,缺点是如果xml过大,消耗内存严重。

SAX(Simple API for XML):基于事件的解析方式,当sax解析器解析到某类型节点时,会触发注册在该节点上的事件。优点是不用将整个xml文档读入内存就可以解析,节约内存。缺点是因为不存在XML结构,需要自己维护节点间的关系,不支持写xml文档,也不支持XPath。

StAX:略

二、XPath

XPath是一种XML查询语言。在mybatis中,解析配置文件和映射文件使用的是DOM解析方式,XPath之于XML等于SQL之于数据库。XPath的表达式语法可以参考其他文档,XPath查找出来的类型包含5种:number、string、boolean、node、nodeset。

三、XPathParser

XPathParser持有XPath、Document、EntityResolver对象用于解析查询xml信息。

  //XPathParser的构建,SqlSessionFactoryBean的buildSqlSessionFactory()方法
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
    //Documnet对象  
    private Document document;
    //是否开启验证
    private boolean validation;
    //加载本地DTD文件,XmlMapperEntityResolver是其实现类
    private EntityResolver entityResolver;
    //mybatis-config.xml配置文件中定义的属性集合
    private Properties variables;
    //XPath对象,用于查询Document节点
    private XPath xpath;
//dom解析xml,创建Documnet对象
private Document createDocument(InputSource inputSource) {
    //......
    return builder.parse(inputSource);
}

在进行xml解析时,会进行xml验证,联网加载xml头部的dtd/xsd约束,当网络环境不好的情况下验证过程会很慢,entityResolver类的作用是将约束文件的加载指向了本地,加快了验证的过程。

  //XmlMapperEntityResolver类 
  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";

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


//使用本地dtd约束代替网络加载,在解析xml文件时执行该操作 
 public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        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());
    }
  }

XPathParser中有许多eval*方法用于查找xml中各种类型的值,其中evalString()方法需要注意,调用PropertyParser.parse()来处理节点中的默认值。需要注意的是mybaits的配置文件、mapper文件所有属性值的解析,都会进入到PropertyParser.parse。

  public String evalString(Object root, String expression) {
    //参数一是XPath表达式,参数二是解析的dom对象,参数三是返回的类型
    String result = (String) evaluate(expression, root, XPathConstants.STRING);
    //该方法会进行默认值的处理
    result = PropertyParser.parse(result, variables);
    return result;
  }

PropertyParser类用于处理属性的默认值,VariableTokenHandler通过variables实行来解析,GenericTokenParser获取占位符的字面值(如${log}->log)。

  public static String parse(String string, Properties variables) {
    //TokenHandler的实现类,可以配置是否使用默认值,
    //属性和默认值的分隔符也可通过进行配置
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    //指定解析的占位符, 使用VariableTokenHandler 类进行解析
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

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

  //解析占位符的字面值:${log} -> log
  public String parse(String text) {
    //...
    builder.append(handler.handleToken(expression.toString()));
    //...
    return builder.toString();
  }

    //从variables属性集合中查找对应的值,没找到则使用设定的默认值
    //${log:LOG4J2}->log:LOG4J2,查找log是否有对应的value,没有则使用LOG4J2
    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) {
            key = content.substring(0, separatorIndex);
            defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
          }
          if (defaultValue != null) {
            return variables.getProperty(key, defaultValue);
          }
        }
        if (variables.containsKey(key)) {
          return variables.getProperty(key);
        }
      }
      return "${" + content + "}";
    }

XPathParser.evalNode返回的是节点类型XNode,XNode是对Node做了进一步的封装,其中parseAttributes(node),parseBody(node)都用到了PropertyParser.parse方法进行占位符的解析工作。

  public XNode(XPathParser xpathParser, Node node, Properties variables) {
    this.xpathParser = xpathParser;
    //org.w3c.dom.Node对象
    this.node = node;
    //节点的名称,如mapper,select等
    this.name = node.getNodeName();
    //mybatis-config.xml中定义的键值对
    this.variables = variables;
    //节点的所有属性集合
    this.attributes = parseAttributes(node);
    //文本节点的文本值,如select节点的sql语句
    this.body = parseBody(node);
  }

三、SqlSessionFactory创建过程:

mybatis的初始化工作中,首先要创建SqlSessionFactory对象,SqlSessionFactoryBean afterPropertiesSet()方式开始通过解析config/mapper配置文件构建SqlSessionFactory对象。

使用了XPathParser对象进行DOM解析,将文档读入内存。

xmlConfigBuilder.parse()解析config配置文件。

xmlMapperBuilder.parse()解析mapper配置文件。

所有的配置都存储在Configuration中,通过Configuration创建SqlSessionFactory对象。

你可能感兴趣的:(Mybatis源码分析)