Mybatis配置文件解析

配置文件解析入口

不通过spring框架集成单独使用mybatis,第一步是读取配置文件构建SqlSessionFactory对象

String resource = "mybatis-config.xml";
InputStream ins  = Resource.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlsessionFactoryBuilder().build(ins);

那么mybatis是如何解析xml配置的呢, 我们根据输入流逐层跟踪

 //XMLConfigBuilder
 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  
  //XPathParser
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  } 
  
  //XPathParser
  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }
  
  //使用DocumentBuilderFactory读取xml
  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);
    }
  }

因此我们可知对xml的解析还是基于JDK的jaxp。

常用java解析xml方式

方式 | 简介|优点|缺点
---|---|---|---|---
DOM解析|以层次结构(类似于树型)来组织节点和信息片段,映射XML文档的结构,允许获取和操作文档的任意部分,是W3C的官方标准|①允许应用程序对数据和结构做出更改②访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。|通常需要加载整个XML文档来构造层次结构,消耗资源大
SAX解析| 流模型中的”推”模型分析方式。通过事件驱动,每发现一个节点就引发一个事件,事件推给事件处理器,通过回调方法完成解析工作,解析XML文档的逻辑需要应用程序完成|①不需要等待所有数据都被处理,分析就能立即开始②只在读取数据时检查数据,不需要保存在内存中③可以在某个条件得到满足时停止解析,不必解析整个文档④效率和性能较高,能解析大于系统内存的文档。|①需要应用程序自己负责TAG的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂②单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持XPath。
JDOM|Java特定的文档对象模型,自身不包含解析器,使用SAX|①使用具体类而不是接口,简化了DOM的API②大量使用了Java集合类,方便了Java开发人员|①没有较好的灵活性②性能较差
DOM4J|简单易用,采用Java集合框架,并完全支持DOM、SAX和JAXP|①大量使用了Java集合类,方便Java开发人员,同时提供一些提高性能的替代方法②支持XPath③有很好的性能|大量使用了接口,API较为复杂

解析流程核心方法

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

这里面有十几个方法,对应着config文件里面的所有一级标签。方法简介如下:
propertiesElement()

解析properties标签,读取我们引入的外部配置文件。这里有两种类型,一种是放在resource目录下的,是相对路径,一种是写的绝对路径。解析的最终结果就是我们会把所有的配置信息放到名为defaults的Properties对象里面,最后把XPathParser和Configuration的Properties属性都设置成我们填充后的Properties对象。

settingsAsProperties()

把settings标签也解析成了Properties对象,对于的子标签的处理在后面。之所以这里先将settings标签解析成Properties对象,是因为下面两个方法要用。

loadCustomVfs(settings)

loadCustomVfs是获取Vitual File System的自定义实现类,比如我们要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。我们根据标签里面的标签,生成一个抽象类VFS的子类,并且赋值到Configuration中。

loadCustomLogImpl(settings)

loadCustomLogImpl是根据标签获取日志的实现类,我们可以用到很多的日志的方案,包括log4j、log4j2、slf4j等,这里生成一个Log接口的实现类,并且赋值到Configuration中。

typeAliasesElement()

接下来,解析标签,它有两种定义方式,一种是直接定义一个类的别名,一种就是指定一个包,那么这个package下面所有的类的名字就会成为这个类全路径的别名。类的别名和类的关系,放在一个TypeAliasRegistry对象里面。

pluginElement()

接下来解析的是标签,比如pageHelper的翻页插件,或者我们自定义的插件。标签里面只有标签,标签里面只有标签。标签解析完以后,会生成一个Interceptor对象,并且添加到Configuration的InterceptorChain属性里面,它是一个List。

objectFactoryElement()、objectWrapperFactoryElement()

这两个标签是用来实例化对象用的,这两个标签,分别生成ObjectFactory、ObjectWrapperFactory对象,同样设置到Configuration属性里面。

reflectorFactory()

解析标签,生成ReflectorFactory对象。

settingsElement(settings)

这里就是对标签里面所有子标签的处理了,前面已经把子标签全部转换成了Properties对象,所以在这里处理Properties对象就可以了。二级标签里面有很多的配置,比如二级缓存,延迟加载,自动生成主键这些。需要注意的是,这些标签的所有的默认值都是在这里赋值的。所有的值都会赋值到Configuration对象里去。

environmentsElement()

这一步解析标签。我们知道,一个environment对应一个数据源,所以在这里我们会根据配置的创建一个事务工厂,根据标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。

databaseIdProviderElement()

解析标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。

typeHandlerElement()

跟typeAlias一样,TypeHandler有两种配置方式,一种是单独配置一个类,一种是指定一个package。最后我们得到的是JavaType和JdbcType,以及用来做相互映射的TypeHandler之间的映射关系。最后放在TypeHandlerRegistry对象里面

posted @ 2021-01-12 13:48  刘66  阅读(69)  评论(0编辑  收藏  举报