mybatis的两大重要组件:配置和映射文件,都是可以通过xml配置的(新版本新增了注解的方式配置Mapper),下面来解析下mybatis是怎么做的

其中,关于配置文件解析的主要是在这个类XMLConfigBuilder里面的parseConfiguration方法,XMLConfigBuilder在初始化的时候会新建一个XPathParser对象用于XML配置文件的解析

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

这面有个重要的类XPathParser和重要的方法evalNode,所以我们引出了这两个类:

org.apache.ibatis.parsing.XNode
org.apache.ibatis.parsing.XPathParser

Mybatis在xml解析这一块用的是java dom API

XNode类是对Node的简单封装,用于表示一个xml 节点,是配置的模型类。其中variables中存放的是变量配置的键值对,用于解析${xx}的字符串(见下文的PropertyParser)xpathParser是一个用于解析节点的解析器,XNode中的大部分方法都是委托给xpathParser对象来进行的。

XPathParser是整个配置的上下文类,document对象表示这个xml的整个文档,variables和XNode一样。

document建立过程:

初始化xpath

XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();

创建document

private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation); factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
} @Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
} @Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}

XPathParser中实现了大量的解析节点的方法和重载:

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

PropertyParser类

在XNode和XPathParser中我们都见过对PropertyParser的使用,主要是根据variables解析字符串中的变量用的,

  private String getBodyData(Node child) {
if (child.getNodeType() == Node.CDATA_SECTION_NODE
|| child.getNodeType() == Node.TEXT_NODE) {
String data = ((CharacterData) child).getData();
data = PropertyParser.parse(data, variables);
return data;
}
return null;
}
  public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}

下面是这个方法的实现,它借助于GenericTokenParser来进行解析,

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

这里引入了一个新的东西:TokenHandler和GenericTokenParser

GenericTokenParser的parse方法用来解析文本(替换参数),openToken是开始标记,这里是"${",closeToken是结束标记,这里是"}"

  public String parse(String text) {
final StringBuilder builder = new StringBuilder();
final StringBuilder expression = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
expression.setLength(0);
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}

上面标红的代码比较重要,其他的代码只是清除噪声,根据openToken和closeToken定位到这个位置,解析出expression,也就是${xxx}的那段字符串,之后交给TokenHandler处理,这里是VariableTokenHandler

    @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) {
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时候指定的XMLMapperEntityResolver类,这个是用于验证xml是否符合dtd的(根据doctype)

/**
* Offline entity resolver for the MyBatis DTDs
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class XMLMapperEntityResolver implements EntityResolver { 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"; /*
* Converts a public DTD into a local one
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
*
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@Override
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());
}
} 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 e) {
// ignore, null is ok
}
}
return source;
} }
 

mybatis 与 xml的更多相关文章

  1. MyBatis Mapper.xml文件中 $和#的区别

    MyBatis Mapper.xml文件中 $和#的区别   网上有很多,总之,简略的写一下,作为备忘.例子中假设参数名为 paramName,类型为 VARCHAR . 1.优先使用#{paramN ...

  2. springboot使用之二:整合mybatis(xml方式)并添加PageHelper插件

    整合mybatis实在前面项目的基础上进行的,前面项目具体整合请参照springboot使用之一. 一.整合mybatis 整合mybatis的时候可以从mybatis官网下载mybatis官网整合的 ...

  3. mybatis mapper.xml 配置文件问题(有的错误xml是不报的) 导致服务无法启动 。

    转载自 开源编程 一舟mybatsi xml编译报错,tomcat启动一直循环,导致内存溢出,启动失败 mapper.xml怎么知道有没有编译错误,哪个位置有错误 这应该是mybatis的一个bug, ...

  4. mybatis的xml文件中如何处理大小于号

    在mybatis的xml配置文件中会遇到大小于号转化的问题,解决问题的方法如下: 1.用转义字符把>和<替换掉 SELECT * FROM test WHERE AND start_dat ...

  5. MyBatis SQL xml处理小于号与大于号

    MyBatis SQL xml处理小于号与大于号 当我们需要通过xml格式处理sql语句时,经常会用到< ,<=,>,>=等符号,但是很容易引起xml格式的错误,这样会导致后台 ...

  6. Mybatis特殊字符处理,Mybatis中xml文件特殊字符的处理

    Mybatis特殊字符处理,Mybatis中xml文件特殊字符的处理 >>>>>>>>>>>>>>>>& ...

  7. mybatis 查询 xml list参数

    mybatis 查询 xml list参数: <select id="getByIds" resultType="string" parameterTyp ...

  8. Java DB 访问之 mybatis mapper xml 配置方式

    1 项目说明 项目采用 maven 组织 ,jdbc 唯一的依赖就是 mysql-connector-java pom 依赖如下: mysql 数据连接 : mysql-connector-java ...

  9. MyBatis笔记----(2017年)最新的报错:Cannot find class [org.apache.commons.dbcp.BasicDataSource] for bean with name 'dataSource' defined in class path resource [com/ij34/mybatis/applicationContext.xml]; nested e

    四月 05, 2017 4:56:11 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRef ...

随机推荐

  1. Integrating AngularJS with RequireJS

    Integrating AngularJS with RequireJS When I first started developing with AngularJS keeping my contr ...

  2. Ubuntu14.04安装build-essential失败,包依赖问题如何解决?

    正在读取软件包列表... 完成 正在分析软件包的依赖关系树        正在读取状态信息... 完成        有一些软件包无法被安装.如果您用的是 unstable 发行版,这也许是 因为系统 ...

  3. PAT 1072. Gas Station (30)

    A gas station has to be built at such a location that the minimum distance between the station and a ...

  4. SHUTDOWN_MSG: Shutting down NameNode at java.net.UnknownHostException: xxx

    刚配置hadoop2.2,格式化namenode时候报的这个错. 原因是hadoop在格式化HDFS的时候,通过hostname命令获取到的主机名在/etc/hosts文件中进行映射的时候,没有找到, ...

  5. 安装Python2.7环境

    1.下载并根据提示安装python_2.7.10_x64 2.将安装目录配置到环境变量path中 3.在命令后台中输入Python,出现下图中信息表示安装成功

  6. linux 下 C语言显示中文

    例如:tset.c int main() { printf("你好,世界\n"); retuen 0; } 编译时应该这样: iconv -f gb2312 -t utf8 tes ...

  7. 未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序 解决方法

    最近在搞asp.net.今天在做数据库操作的时候,老发生错误,还以为是自己代码有问题,检查了好久都发现错误. 错误提示: 未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程 ...

  8. 特殊的对象引用---$this

    只要是对象中的成员,必须使用这个对象($this)来访问到这个对象内部的属性和方法 特殊对象的引用$this就是再对象内部的成员方法中,代表本对象的一个引用,但智能在对象的成员方法中使用,不管是在对象 ...

  9. Word Break II

    Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each ...

  10. 瀑布流布局(jq实现)

    参考刘晓帆大神的瀑布流效果 自己也写了个 希望能给打算写这个效果的朋友一点点思路,也希望朋友们批评指正! html代码: <!DOCTYPE html> <html> < ...