解析器模块的功能主要是将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对象。