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. 模块化以及requirejs和seajs

    我们用模块化的思想进行网页的编写是为了更好的管理我们的项目 模块与模块之间是独立存在的,每个模块可以独立的完成一个子功能. 模块化所涉及的规范commonjs,AMD,CMD,UMD 其中的commo ...

  2. varnish 内置函数详细说明

    Subroutine列表 •vcl_recv 在请求开始时候被调用,在请求已经被接收到并且解析后调用.目的就是决定是否处理这个请求,怎么处理,使用哪个后端.vcl_recv以return结束,参数可以 ...

  3. Android实战技巧:ViewStub的应用

    在开发应用程序的时候,经常会遇到这样的情况,会在运行时动态根据条件来决定显示哪个View或某个布局.那么最通常的想法就是把可能用到的View都写在上面,先把它们的可见性都设为View.GONE,然后在 ...

  4. Intellj IDEA Java随笔

    敲击sout+Tab会出现:System.out.println(); int(-2147483648, 2147483647) 当一个方法类型不是void时,那么我们不仅要考虑它正确时的返回值,错误 ...

  5. 【LeetCode】Product of Array Except Self

    Product of Array Except Self Given an array of n integers where n > 1, nums, return an array outp ...

  6. libev学习(一)

    一.libev简介 Libev是一个事件循环:你注册感兴趣的特定事件(比如一个文件可以读取时或者发生超时时),它将管理这些事件源,将这些事件反馈给你的程序.为了实现这些,至少要在你的进程(或线程)中执 ...

  7. Android 学习第16课,java 包、类等相关的一些基础知识

    1.建议将类放在包中,不要使用无名包 2.建议包名都用小写单词组成,不要用大写 3.建议包名用“域名的倒写.项目名.模块名”的形式,以确保包名的唯一性 注意:类变量与实例变量.类方法与实例方法的区别 ...

  8. vs2013提高编译速度

    最近做新的项目,很多库是之前项目积累下来的库.在windows下使用的IDE是vs2013,整体编译的时候,明显感觉编译速度较慢,文件是一个一个编的. 从编译器本身的角度,vs2013是提供了加快编译 ...

  9. 利用 iframe解决ajax的跨域问题

    问题 1. form提交或a标签跳转方式提交不会引发跨域问题. 2. ajax出于安全问题就有了跨域问题,因为一次请求中既访问了外部域最后返回了自己的域. 3. 用iframe其实就是想仿照ajax的 ...

  10. 基于云计算Iaas平台的ZStack

    2015年4月,一家全新的基础架构即服务的软件产品ZStack面世.ZStack的主创人员是自在海外云计算公司的中国人.ZStack是基于Java语言,结合了OpenStack和CloudStack上 ...