与大多数ORM框架一样,iBatis2也是用Xml描述ORM映射信息(在annotations出现之前),那么这些XML配置信息是怎么解析呢?呵呵,大部分人看到这儿可能会说:这有啥难的,用DOM或者SAX解析xml都是很容易的事!确实iBatis解析xml的方法也无外乎这二者之一,不过仔细读过iBatis解析XML的源码,我发现iBatis解析xml的代码很值得我们学习……
iBatis中最重要的一个接口是SqlMapClient,首先看看在程序中是怎么样同过配置文件得到SqlMapClient对象的:
static {
try {
String resource = "com/ppsoft/ibatis/test/config/SqlMapConfig.xml";
Reader reader = Resources.getResourceAsReader (resource);
sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException ("Error initializing MyAppSqlConfig class. Cause:"+e);
}
}
类SqlMapClientBuilder提供了几个静态方法,用于读取iBatis配置文件并创建
SqlMapClient对象,这一章主要是分析iBatis是如何读取配置文件,所以也只看解析xml文件的部分,那再看看buildSqlMapClient方法中都做了些什么事:
public static SqlMapClient buildSqlMapClient(Reader reader) {
return new SqlMapConfigParser().parse(reader);
}
首先创建一个SqlMapConfigParser,
调用新创建的
SqlMapConfigParser对象的parser方法,将解析xml和创建
SqlMapClient的工作委托给
SqlMapConfigParser对象。那么接下来看看
SqlMapConfigParser的parse方法都干了些
啥:
public SqlMapClient parse(Reader reader) {
try {
usingStreams = false;
parser.parse(reader);
return state.getConfig().getClient();
} catch (Exception e) {
throw new RuntimeException("Error occurred. Cause: " + e, e);
}
}
呵呵,看到这里你会发现,其实SqlMapConfigParser的parse方法也没干啥,只是将解析工作委托给
SqlMapConfigParser的一个parser属性,看看
SqlMapConfigParser的parser属性是啥东西:
protected final NodeletParser parser = new NodeletParser();
//state用于存储所有解析出来的信息
private XmlParserState state = new XmlParserState();
原来parser属性是一个NodeletParser对象,xml就是由NodeletParser这个类解析的,这个类时下面分析的重点。那我们再来看看NodeletParser这个类的parse方法是如何解析xml的:
public void parse(Reader reader) throws NodeletException {
try {
Document doc = createDocument(reader);
parse(doc.getLastChild());
} catch (Exception e) {
throw new NodeletException("Error parsing XML. Cause: " + e, e);
}
}
首先创建document对象(调用JAXP创建的,并且根据DTD文件检验了xml的格式是否正确),然后调用NodeletParser中的另外一个重载的parse方法:
public void parse(Node node) {
Path path = new Path();
processNodelet(node, "/");
process(node, path);
}
先创建一个Path对象(Path是NodeletParser中定义的一个内部类),然后调用processNodelet方法,最后调用了process(node,path);先看看processNodelet方法干嘛啦?
private void processNodelet(Node node, String pathString) {
Nodelet nodelet = (Nodelet) letMap.get(pathString);
if (nodelet != null) {
try {
nodelet.process(node);
} catch (Exception e) {
throw new RuntimeException("Error parsing XPath '" + pathString + "'. Cause: " + e, e);
}
}
}
参数pathString实际上是个xpath字符串,从这段代码可以看出NodeletParser有个letMap的属性,是一个Map,以xpath为key,Nodelet对象为value。这段代码逻辑是:根据传入的xpath查找letMap有没有对应的Nodelet对象,如果有就调用对应Nodelet对象的process方法,参数为要处理的Node。那么这个Nodelet到底是什么东西呢?看看代码就知道啦:
public interface Nodelet {
void process (Node node) throws Exception;
}
原来只是个接口而以,将对节点的处理抽象出来,这个设计很高明:将节点处理方法抽象成Nodelet接口,sqlMap中存储处理每个Node的Nodelet对象(我们可以称之为Node处理器),key为Node的xpath,如果我们指定好每个Node的处理器对象,那么只需要遍历所有的节点,并到sqlMap查找对应Nodelet对象调用其process方法即可完成对xml的解析处理。
下面我们来分析下process(node,path)方法做了些什么事情。看这个方法的代码前先得看看Path这个类时干嘛:
private static class Path {
private List nodeList = new ArrayList();
public Path() {
}
public Path(String xpath) {
StringTokenizer parser = new StringTokenizer(path, "/", false);
while (parser.hasMoreTokens()) {
nodeList.add(parser.nextToken());
}
}
public void add(String node) {
nodeList.add(node);
}
//删除xpath路径中的最后一个节点
public void remove() {
nodeList.remove(nodeList.size() - 1);
}
public String toString() {
StringBuffer buffer = new StringBuffer("/");
for (int i = 0; i < nodeList.size(); i++) {
buffer.append(nodeList.get(i));
if (i < nodeList.size() - 1) {
buffer.append("/");
}
}
return buffer.toString();
}
}
看看源码就知道,这个类实际上只是用来描述xpath的,xpath中的所有节点都顺序存放,在一个List中,并复写了toString方法,将List转换为Xpath字符串,另外,Path类提供了两个重要的方法add和remove,add用于添加子节点,如果原来的xpath是/root,调用add("element1")后,path就成为/root/element1;remove方法用于删除path中的最后一个节点,与add相反。
再分析下process(node,path)代码,代码如下:
private void process(Node node, Path path) {
if (node instanceof Element) {
// Element
String elementName = node.getNodeName();
path.add(elementName);
processNodelet(node, path.toString());
processNodelet(node, new StringBuffer("//").append(elementName).toString());
// 处理节点的所有Attribute
NamedNodeMap attributes = node.getAttributes();
int n = attributes.getLength();
for (int i = 0; i < n; i++) {
Node att = attributes.item(i);
String attrName = att.getNodeName();
path.add("@" + attrName);
processNodelet(att, path.toString());
processNodelet(node, new StringBuffer("//@").append(attrName).toString());
path.remove();
}
// 递归遍历处理所有node的Children
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
process(children.item(i), path);
}
//node以及其子节点处理结束,调用node的end()处理器
path.add("end()");
processNodelet(node, path.toString());
path.remove();
path.remove();
} else if (node instanceof Text) {
// 如果是Text
path.add("text()");
processNodelet(node, path.toString());
processNodelet(node, "//text()");
path.remove();
}
}
代码中加了些简单注释,仔细看看就明白,process(Node node,Path path)方法递归遍历了node、node的所有属性和node的所有子节点,并调用processNodelet,这也印证了前面的推测的正确性(请看前面对processNodelet方法的分析)。
根据上面的分析,我们知道知道xml中每个Node的信息的处理方法(前面提到过Node信息处理抽象为接口Nodelet)都以Node的xpath为key存放在NodeletParser类的letMap中,那么我们如何为每个Node注册处理器(Nodelet对象)呢?
让我们回到SqlMapConfigParser的代码看看,首先看看SqlMapConfigParser的构造方法:
public SqlMapConfigParser() {
parser.setValidation(true);
//设置DTD文件的classpath映射
parser.setEntityResolver(new SqlMapClasspathEntityResolver());
//注册Node处理器
addSqlMapConfigNodelets();
addGlobalPropNodelets();
addSettingsNodelets();
addTypeAliasNodelets();
addTypeHandlerNodelets();
addTransactionManagerNodelets();
addSqlMapNodelets();
addResultObjectFactoryNodelets();
}
可以看到上面的构造方法中一大半的代码是addXXX形式,这个就是给xml文档的Node注册处理器(Nodelet对象),
我们随便看一个addXXX方法,看里面是怎么注册Node处理器的,就看SqlMapConfigParser的addTypeAliasNodelets()方法吧:
/**
* 注册typeAlias处理器
*/
private void addTypeAliasNodelets() {
parser.addNodelet("/sqlMapConfig/typeAlias", new Nodelet() {
public void process(Node node) throws Exception {
Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
String alias = prop.getProperty("alias");
String type = prop.getProperty("type");
state.getConfig().getTypeHandlerFactory().putTypeAlias(alias, type);
}
});
}
这里调用了类SqlMapConfigParser的parser属性(这里的parser属性就是前面说的NodeletParser的一个实例)的addNodelet方法,原来是调用这个方法给xml的节点注册处理器的。NodeletParser的方法addNodelet的第一个参数是一个xpath,用于表示xml中的Node;第二个参数是节点处理器(Nodelet对象),用于处理xpath指定的xml节点,这里的Nodelet是使用匿名内部类实现的;我们看看NodeletParser的addNodelet方法的代码:
public void addNodelet(String xpath, Nodelet nodelet) {
letMap.put(xpath, nodelet);
}
Nodelet对象就是在letMap中映射的!
再看看这里对/sqlMapConfig/typeAlias是怎么处理的:
- 首先通过NodeletUtils的parseAttribute方法计算出/sqlMapConfig/typeAlias节点的所有属性值,以Properties对象返回,属性名为key,属性值为value,这里会把属性值中带有${}这样的表达式值计算出来。
- 从返回的属性值的Properties对象中获取/sqlMapConfig/typeAlias节点的alias和type属性。
- 将提取的typeAlias信息存入TypeHanderFactory的别名映射表中。
其他Node的处理器以同样的方式注册到NodeletParser对象中。往NodeletParser对象注册了所有需要的 Nodelet处理器之后,调用NodeletParser的parser方法就可以将xml解析出来,具体每个节点是怎么处理的是有我们自己指定的,NodeletParser只是定义了如何遍历xml中所有节点的方法。
NodeletParser实际上应用了模板方法模式的思想,在NodeletParser中定义了如何遍历xml中所有节点的方法,但是没有定义节点是如何处理的,而是通过使用指定Node的Nodelet处理器,当遍历节点时就去调用对应的Nodelet的process方法,从而达到代码的复用,做到与具体xml文件无关。
com.ibatis.sqlmap.engine.builder.xml包下还有个类SqlMapParser,用于对SqlMap文件的解析,解析的方式和SqlMapConfigParser一样,也是通过NodeletParser,向NodeletParser对象注册Nodelet处理器实现对SqlMap文件的解析。
下面的类图是iBatis解析SqlMapConfig文件和SqlMap文件的几个最核心的几个类之间的关系:
iBatis的xml解析模块基本已经很明了,认真分析完这些代码后,我第一次感受到看别人设计优良的代码的乐趣,后期计划继续读完iBatis的源代码,当然也会写下我的所感所悟~
- 大小: 33.2 KB
分享到:
相关推荐
通过深入分析iBATIS的源码,开发者不仅可以了解其工作原理,还能学习到设计模式、数据库访问的最佳实践以及如何优雅地处理数据库操作。对于提升Java开发者的技能和理解数据库访问层的实现有极大的帮助。在实际开发中...
标题中的“ibatis 连接字符串 SqlMapConfig.xml”指的是使用iBATIS(一个轻量级的Java持久层框架)时,配置数据库连接的关键文件——SqlMapConfig.xml。这个文件是iBATIS的核心配置文件,它包含了数据源、事务管理器...
解析这个XML文件的过程涉及到DOM或SAX解析器,源码中这部分功能通常在`org.apache.ibatis.io.Resources`和`org.apache.ibatis.builder.Configuration`类中实现。 三、Executor执行器 Executor执行器是iBatis的核心...
《ibatis框架源码剖析》是一本深入探讨mybatis前身——ibatis的源码解析书籍。通过对源码的深入分析,我们可以理解ibatis的核心机制,掌握数据库操作的底层原理,从而更好地利用和优化这个强大的持久层框架。在这个...
iBATIS框架源码剖析
描述中的"ibatis框架源码剖析书中附带的光盘,ibatis源码分析"暗示这可能是一个学习资源,用于深入理解iBATIS的工作原理,可能包括了对源码的详细解读和分析。 **iBATIS核心知识点** 1. **SQL映射**:iBATIS的核心...
iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。于2010年6月16号被谷歌托管,改名为MyBatis。是一个基于SQL映射支持Java和·NET的持久层框架。
在源码分析部分,你会看到iBATIS如何加载和解析XML配置文件,如何执行SQL语句,以及如何处理异常。这部分内容对于理解iBATIS的工作流程至关重要,它将帮助你更好地调试和优化基于iBATIS的应用。 最后,你会了解到...
2. **源码**:这里提供的不仅是编译后的类库,而是未经编译的原始代码,可供学习和分析。 3. **code ibatis**:强调这是iBATIS项目的代码,对于熟悉或使用iBATIS的人来说,这是一个重要的标识。 【压缩包子文件的...
本项目深入分析了基于Java语言的iBatis框架设计源码,包含20个文件,涵盖4个XML配置文件、3个Java源文件、2个JAR包文件以及其他相关文件类型。iBatis作为一款流行的持久层框架,旨在简化Java应用程序的数据访问层...
通过学习和分析这个源码,开发者不仅可以深入了解SpringMVC和iBatis的协同工作原理,还可以掌握如何在Eclipse这样的IDE中配置和运行这样的项目。这有助于提升对MVC模式的理解,提高数据库操作的能力,以及熟练运用...
1. **创建XML配置文件**:在项目中创建一个名为`mybatis-config.xml`的文件,这是iBATIS的全局配置文件,用于定义数据源、事务管理器等。同时,也需要为每个Mapper创建单独的XML文件,如`UserMapper.xml`,其中包含...
`ibatis2mybatisConverter` 是一个工具,旨在帮助开发者将 iBatis 2 的 SQLMap XML 文件无缝迁移到 Mybatis 3。 在 iBatis 2 中,SQLMap XML 文件包含了数据库交互的核心元素,如 SQL 查询、结果映射、事务管理和...
本书籍“iBATIS 框架源码剖析”提供了对iBATIS框架深入理解的机会,通过源代码分析,帮助读者掌握其内部工作原理。源代码带有详尽的注释,使得学习过程更为直观和高效。 iBATIS的核心概念主要有以下几个方面: 1. ...
本项目深入解析了基于Java语言的iBATIS 2.x框架设计源码,包含292个Java源文件、25个XML配置文件、11个SQL语句、5个属性文件、4个YAML文件、2个DTD文件及其他相关文件,共计348个文件。
2. DAO(Data Access Object):是iBATIS中的一个设计模式,用于封装数据库操作。DAO接口定义了操作数据库的方法,而具体的实现则由iBATIS处理。 3. 映射器接口:开发者定义的接口,其方法对应SQL映射文件中的SQL...
《深入解析iBatis-2源代码》 iBatis,作为一个轻量级的持久层框架,曾经在Java开发领域中占据了重要的地位。它将SQL语句与Java代码分离,提高了开发效率,降低了维护难度。本篇文章将针对从Apache网站通过SVN下载的...
Ibatis 是一个优秀的开源持久层框架,主要用于简化Java应用程序与数据库之间的交互。它将SQL语句与业务逻辑分离,提供了一种灵活的映射机制,使得开发者可以更自由地控制SQL执行。本资源包含了Ibatis的源码、API文档...