Mybatis基础支持层-解析器模块

Mybatis 解析器

Posted by Claire on June 13, 2020

Mybatis基础支持层-解析器模块

Mybatis三层架构:接口层,核心处理层,基础支持层 基础支持层:数据源模块、反射模块、缓存模块、日志模块、事务管理模块、Binding模块、类型转换、资源解析、解析器模块

一 XML内容解析

  • 之前有一篇博文,详细描述了多种XML解析方式:Dom SAX StAX

  • XPath: 使用路径表达式来选取XML文档中指定的节点或者节点的集合

nodename  选取指定节点的所有子节点
/         从根节点选取指定节点
//        根据指定表达式,在整个文档中选取匹配的节点
.         选取当前节点
..        选取当前节点的父节点
@         选取属性
*         匹配任何元素节点
@*        匹配任何属性节点
node()    匹配任何类型的节点
text()    匹配文本节点
|         选取若干个路径
[]        指定某个条件
  • 使用示例
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse("store.xml");
        List<Product> products = new ArrayList<>();
        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xPath = xPathFactory.newXPath();
        XPathExpression expression = xPath.compile("//product[name='薯片']/price/text()");
        Double price = (Double)expression.evaluate(document,XPathConstants.NUMBER);
        System.out.println(price);

        NodeList nodeList = (NodeList) xPath.evaluate("//product[@id>2]/name/text()", document, XPathConstants
                .NODESET);
        for(int i =0 ;i<nodeList.getLength() ;i++){
            System.out.println(nodeList.item(i).getNodeValue());
        }

二 XPathParser

  • 封装了XPath / Document / EntityResolver
 public class XPathParser {
    /**
     * Document对象
     */
    private final Document document;
       /**
     * 是否开启验证
     */
    private boolean validation;
       /**
     * 用于加载本地DTD文件
     */
    private EntityResolver entityResolver;
       /**
     * mybatis-config.xml中>properties> 标签定义的键值对集合
     */
    private Properties variables;
       /**
     * XPath 对象
     */
    private XPath xpath;

    ...
 }   
  • 为了避免远程加载DTD文件,EntityResolver会从本地加载一份, Mybatsi中有XMLMapperEntityResolver的接口实现
 public class XMLMapperEntityResolver implements EntityResolver {
     //mybatis-config.xml文件和映射文件对应的DTD的systemId
    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";

    public XMLMapperEntityResolver() {
    }

//核心实现的方法
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
        try {
            if (systemId != null) {
                //根据SystemId获取DTD文件
                String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
                if (lowerCaseSystemId.contains("mybatis-3-config.dtd") || lowerCaseSystemId.contains("ibatis-3-config.dtd")) {
                    return this.getInputSource("org/apache/ibatis/builder/xml/mybatis-3-config.dtd", publicId, systemId);
                }

                if (lowerCaseSystemId.contains("mybatis-3-mapper.dtd") || lowerCaseSystemId.contains("ibatis-3-mapper.dtd")) {
                    return this.getInputSource("org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd", publicId, systemId);
                }
            }

            return null;
        } catch (Exception var4) {
            throw new SAXException(var4.toString());
        }
    }

    private InputSource getInputSource(String path, String publicId, String systemId) {
        InputSource source = null;
        if (path != null) {
            try {
                InputStream in = Resources.getResourceAsStream(path);
                source = new InputSource(in);
                source.setPublicId(publicId);
                source.setSystemId(systemId);
            } catch (IOException var6) {
            }
        }

        return source;
    }
}

  • 正式加载XML文档,XPathParser的createDocument方法,代码是之前比较常见的XML文件的解析步骤
  private Document createDocument(InputSource inputSource) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(this.validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(this.entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void fatalError(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                public void warning(SAXParseException exception) throws SAXException {
                }
            });
            return builder.parse(inputSource);
        } catch (Exception var4) {
            throw new BuilderException("Error creating document instance.  Cause: " + var4, var4);
        }
    }
      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();
    }
  • XPathParser.evalString() 会处理节点中的默认值
  public String evalString(Object root, String expression) {
        String result = (String)this.evaluate(expression, root, XPathConstants.STRING);
        //设置默认值
        result = PropertyParser.parse(result, this.variables);
        return result;
    }
  • PropertyParser中 static final String KEY_ENABLE_DEFAULT_VALUE = “org.apache.ibatis.parsing.PropertyParser.enable-default-value”; 对应mybatis-config.xml文件中< properties > 中是否配置了开启默认值功能

  • PropertyParser.parse最终将解析的任务交给GenericTokenParser parser.parse实现

  public static String parse(String string, Properties variables) {
        PropertyParser.VariableTokenHandler handler = new PropertyParser.VariableTokenHandler(variables);
        GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
        return parser.parse(string);
    }
  • GenericTokenParser 是一个通用字符占位符解析器,基本就是字符串匹配,有开始占位符,结束占位符,对应如何解析,逐个查找开始和结束符号,最终将参数拿到
public class GenericTokenParser {
    private final String openToken;
    private final String closeToken;
    private final TokenHandler handler;
    ...
}
  • TokenHandler有四个实现类(VariableTokenHandler,ParameterMappingTokenHandler,DynamicCheckerTokenParser,BindingTokenParser),分别用于解析输入参数、动态检查、参数绑定、变量,对于开启检测的变量解析,会使用到VariableTokenHandler,DynamicCheckerTokenParser
private static class VariableTokenHandler implements TokenHandler {
        private final Properties variables;
        private final boolean enableDefaultValue;
        private final String defaultValueSeparator;
...
 public String handleToken(String content) {
     //按照默认的分隔符,进行字符串查找,分割出占位符名称,塞入Properties中,如果开启默认值形式,并且解析未果,则会填入默认值
 }
}
  • 因而 如果是参数${usernamae:root},首先按照 : 分割,获取username字段,接着在variables里面查找值,如果不存在填入root的值,放入properties中

  • 前面XPathParser 还有evalNode ,获取一个XNode 是对Node的封装,会绑定Properties 于XPathParser对象。 XNode中的parseAttributes parseBody都是用了getAttributes 于getChildNodes的熟悉方法实现的,XNode 的eval()方法,也是和XPath的eval()方法是类似的