配置文件解析是使用iBatis的第一步。那么,ibatis是如何实现其配置文件解析呢?本文将在较高的抽象层次上讲述ibatis配置文件解析的总体流程。
一切都从new SqlMapConfigParser().parse(reader);这条语句开始的。这条语句包含了ibatis配置文件解析的全部内容。这条语句包括两部分内容:创建sqlMapConfigParser对象和执行器parse方法。下面就分别来讲述这两部分内容究竟做了些什么工作。
首先看一下SqlMapConfigParser对象创建时执行了哪些操作。
protected final NodeletParser parser = new NodeletParser(); private XmlParserState state = new XmlParserState(); public SqlMapConfigParser() { //验证 parser.setValidation(true); //定位DTD文件 parser.setEntityResolver(new SqlMapClasspathEntityResolver()); //Nodelet注册 addSqlMapConfigNodelets(); addGlobalPropNodelets(); addSettingsNodelets(); addTypeAliasNodelets(); addTypeHandlerNodelets(); addTransactionManagerNodelets(); addSqlMapNodelets(); addResultObjectFactoryNodelets(); }
第一个操作:创建NodeletParser对象,这个类定义了配置文件解析的通用模板方法,即如何遍历配置文件并进行处理。
第二个操作:创建XmlParserState对象,这个类的用途暂时可以先不关注。
第三个操作:执行构造函数。在构造函数中进行了配置文件的验证,以及Nodelet的注册。这里就会有两个疑问了,Nodelet是干什么的,被注册到哪里了。
先说明一下Nodelet的作用,Nodelet是ibatis节点解析的抽象接口,定义如下:
public interface Nodelet { void process (Node node) throws Exception; }
一个Nodelet接口的实现类能够处以一类xml节点。然后就是Nodelet实现类的注册了,在SqlMapConfigParser类的构造函数中,将SqlMapConfig配置文件相关的节点的解析类(Nodelet的实现类)注册到letMap中。这是letMap是一个Map,是NodeletParser的一个属性,其key为xpath,value为Nodelet实现类的实例。这个注册后letMap会在xml文件解析中被使用。
以上这些操作都是在为配置文件的解析做准备,接下来就要看ibatis是如何解析配置文件了。先看SqlMapConfigParser的parse方法,它调用了NodeletParser的parse方法,下面看一下具体的实现代码:
//解析根节点 public void parse(Node node) { Path path = new Path(); processNodelet(node, "/"); process(node, path); } //解析单个节点参数node为被解析的节点,参数pathString是该节点对应的xpath信息 private void processNodelet(Node node, String pathString) { //从letMap中根据xpath取得之前注册的Nodelet实例 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); } } } //处理xml配置文件,最开始是从根路径开始的 //参数path是用来生成xpath的 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(); } // Children NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { //递归解析子节点 process(children.item(i), path); } 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(); } }
以上代码即为ibatis解析配置文件的主要流程了,至于单个节点如何解析和解析后数据存储在什么地方在这里暂不做详细讨论。下面来总结一下ibatis解析配置文件的大体思路:
1. 定义了Nodelet接口,可以处理各种类型的xml节点。
2. 准备工作,注册Nodelet实例,将xpath和Nodelet实例关联起来。
3. 解析,在NodeletParser中,递归解析处理配置文件,根据Node信息生成xpath,根据xpath取得Nodelet实例,并执行其process方法,从而完成了配置文件的解析工作。