`
pxlfxl2
  • 浏览: 51069 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

iBatis2源码分析(一)——xml解析模块

阅读更多

       与大多数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是怎么处理的:

  1. 首先通过NodeletUtils的parseAttribute方法计算出/sqlMapConfig/typeAlias节点的所有属性值,以Properties对象返回,属性名为key,属性值为value,这里会把属性值中带有${}这样的表达式值计算出来。
  2. 从返回的属性值的Properties对象中获取/sqlMapConfig/typeAlias节点的alias和type属性。
  3. 将提取的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框架源码剖析源码

    通过深入分析iBATIS的源码,开发者不仅可以了解其工作原理,还能学习到设计模式、数据库访问的最佳实践以及如何优雅地处理数据库操作。对于提升Java开发者的技能和理解数据库访问层的实现有极大的帮助。在实际开发中...

    ibatis 连接字符串 SqlMapConfig.xml

    标题中的“ibatis 连接字符串 SqlMapConfig.xml”指的是使用iBATIS(一个轻量级的Java持久层框架)时,配置数据库连接的关键文件——SqlMapConfig.xml。这个文件是iBATIS的核心配置文件,它包含了数据源、事务管理器...

    ibatis源码,ibatis源码 ibatis源码 ibatis源码

    解析这个XML文件的过程涉及到DOM或SAX解析器,源码中这部分功能通常在`org.apache.ibatis.io.Resources`和`org.apache.ibatis.builder.Configuration`类中实现。 三、Executor执行器 Executor执行器是iBatis的核心...

    ibatis框架源码剖析光盘资料

    《ibatis框架源码剖析》是一本深入探讨mybatis前身——ibatis的源码解析书籍。通过对源码的深入分析,我们可以理解ibatis的核心机制,掌握数据库操作的底层原理,从而更好地利用和优化这个强大的持久层框架。在这个...

    iBATIS框架源码剖析

    iBATIS框架源码剖析

    ibatis源码

    描述中的"ibatis框架源码剖析书中附带的光盘,ibatis源码分析"暗示这可能是一个学习资源,用于深入理解iBATIS的工作原理,可能包括了对源码的详细解读和分析。 **iBATIS核心知识点** 1. **SQL映射**:iBATIS的核心...

    iBatis框架源码剖析

    iBATIS一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目。于2010年6月16号被谷歌托管,改名为MyBatis。是一个基于SQL映射支持Java和·NET的持久层框架。

    iBATIS框架源码剖析pdf第二部分

    在源码分析部分,你会看到iBATIS如何加载和解析XML配置文件,如何执行SQL语句,以及如何处理异常。这部分内容对于理解iBATIS的工作流程至关重要,它将帮助你更好地调试和优化基于iBATIS的应用。 最后,你会了解到...

    ibatis2.3源码

    2. **源码**:这里提供的不仅是编译后的类库,而是未经编译的原始代码,可供学习和分析。 3. **code ibatis**:强调这是iBATIS项目的代码,对于熟悉或使用iBATIS的人来说,这是一个重要的标识。 【压缩包子文件的...

    基于Java语言的ibatis设计源码分析

    本项目深入分析了基于Java语言的iBatis框架设计源码,包含20个文件,涵盖4个XML配置文件、3个Java源文件、2个JAR包文件以及其他相关文件类型。iBatis作为一款流行的持久层框架,旨在简化Java应用程序的数据访问层...

    springMVC+ibatis的源码

    通过学习和分析这个源码,开发者不仅可以深入了解SpringMVC和iBatis的协同工作原理,还可以掌握如何在Eclipse这样的IDE中配置和运行这样的项目。这有助于提升对MVC模式的理解,提高数据库操作的能力,以及熟练运用...

    ibatis用xml配置文件配置使用

    1. **创建XML配置文件**:在项目中创建一个名为`mybatis-config.xml`的文件,这是iBATIS的全局配置文件,用于定义数据源、事务管理器等。同时,也需要为每个Mapper创建单独的XML文件,如`UserMapper.xml`,其中包含...

    ibatis2mybatisConverter:将 sqlMap xmls 从 iBatis 2 迁移到 Mybatis 3

    `ibatis2mybatisConverter` 是一个工具,旨在帮助开发者将 iBatis 2 的 SQLMap XML 文件无缝迁移到 Mybatis 3。 在 iBatis 2 中,SQLMap XML 文件包含了数据库交互的核心元素,如 SQL 查询、结果映射、事务管理和...

    ibatis 框架源码剖析 书籍源代码 带有详尽注释

    本书籍“iBATIS 框架源码剖析”提供了对iBATIS框架深入理解的机会,通过源代码分析,帮助读者掌握其内部工作原理。源代码带有详尽的注释,使得学习过程更为直观和高效。 iBATIS的核心概念主要有以下几个方面: 1. ...

    基于Java语言的iBATIS 2.x设计源码深度解析

    本项目深入解析了基于Java语言的iBATIS 2.x框架设计源码,包含292个Java源文件、25个XML配置文件、11个SQL语句、5个属性文件、4个YAML文件、2个DTD文件及其他相关文件,共计348个文件。

    iBATIS2.3.4 jar包及源码

    2. DAO(Data Access Object):是iBATIS中的一个设计模式,用于封装数据库操作。DAO接口定义了操作数据库的方法,而具体的实现则由iBATIS处理。 3. 映射器接口:开发者定义的接口,其方法对应SQL映射文件中的SQL...

    ibatis-2 源代码

    《深入解析iBatis-2源代码》 iBatis,作为一个轻量级的持久层框架,曾经在Java开发领域中占据了重要的地位。它将SQL语句与Java代码分离,提高了开发效率,降低了维护难度。本篇文章将针对从Apache网站通过SVN下载的...

    ibatis源码+api文档+jar包

    Ibatis 是一个优秀的开源持久层框架,主要用于简化Java应用程序与数据库之间的交互。它将SQL语句与业务逻辑分离,提供了一种灵活的映射机制,使得开发者可以更自由地控制SQL执行。本资源包含了Ibatis的源码、API文档...

Global site tag (gtag.js) - Google Analytics