MyBatis源码分析:如何解析配置文件

2016.04.21 | Comments

MyBatis可以使用xml或者注解的方式进行配置,不管是哪种方式,最终会将获取到的配置参数设置到Configuration类中,例如,SqlSessionFactoryBuilder类中就是通过解析XML来创建Configuration

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

MyBatis通过SqlSessionFactoryBuilder作为入口,通过传入配置文件,使用了BaseBuilder实现类进行配置文件解析,具体实现类是XMLConfigBuilder,在这里MyBatis对配置的项进行了全面解析,只不过不是所有的解析都放在了XMLConfigBuilder,XMLConfigBuilder解析了二级节点,并作为一个总入口,还有另外几个类继承了BaseBuilder,用于解析不同的配置。而解析到的配置项信息,基本都保存在了Configuration这个类,可以看到多处地方依赖到它。

XMLConfigBuilder类位于org.apache.ibatis.builder中,我们来看看该包下有哪些类及每个类实现过程。

XMLConfigBuilder是继承BaseBuilder的,BaseBuilder是一个抽象类,提供了一些protected的方法和三个属性:

  • Configuration
  • TypeAliasRegistry:从Configuration中获取
  • TypeHandlerRegistry:从Configuration中获取

BaseBuilder的子类有:

  • XMLConfigBuilder:解析MyBatis config文件
  • XMLMapperBuilder:解析SqlMap映射文件
  • XMLStatementBuilder:解析sql语句
  • SqlSourceBuilder
  • MapperAnnotationBuilder:解析注解
  • MapperBuilderAssistant:负责解析配置和mapping包下的接口交换

这里为什么不使用接口呢?

XMLConfigBuilder是用于解析XML配置文件,解析XML用的是sax方式,相关代码在org.apache.ibatis.parsing包下面,该包下面代码比较独立,可以复用到自己的项目中去,你懂的。

XMLConfigBuilder提供了几个构造方法,主要是为了初始化XPathParser。真正开始解析文件从下面代码开始:

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

该方法是调用parseConfiguration方法开始解析/configuration节点。

private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectionFactoryElement(root.evalNode("reflectionFactory"));
    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);
  }
}

该方法包括以下几个步骤:

  • 解析settings节点
  • 解析properties
  • 解析typeAliases
  • 解析plugins
  • 解析objectFactory
  • 解析objectWrapperFactory
  • 解析environments
  • 解析databaseIdProvider
  • 解析typeHandlers
  • 解析mappers,调用XMLMapperBuilder进行解析

其中,最复杂的是解析mappers:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        //添加包下面的mapper
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        //三个参数只能设置一个
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

分析XMLMapperBuilder:

private XPathParser parser;
private MapperBuilderAssistant builderAssistant;
private Map<String, XNode> sqlFragments;
private String resource;

XMLMapperBuilder有四个属性,一个XMLMapperBuilder对应一个资源文件,需要一个XPath解析器和一个Mapper构建的帮助类,并保存sql片段。

提供了几个构造方法类初始化属性:

@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
  this(reader, configuration, resource, sqlFragments);
  this.builderAssistant.setCurrentNamespace(namespace);
}

@Deprecated
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()),
      configuration, resource, sqlFragments);
}

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
  this(inputStream, configuration, resource, sqlFragments);
  this.builderAssistant.setCurrentNamespace(namespace);
}

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
      configuration, resource, sqlFragments);
}

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  super(configuration);
  this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
  this.parser = parser;
  this.sqlFragments = sqlFragments;
  this.resource = resource;
}

XMLMapperBuilder真正开始解析是在parse方法:

public void parse() {
  //先判断文件是否加载
  if (!configuration.isResourceLoaded(resource)) {
    //解析/mapper节点
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //绑定mapper到命名空间
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingChacheRefs();
  parsePendingStatements();
}

configurationElement方法如下:

private void configurationElement(XNode context) {
  try {
    //设置命名空间

    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    //cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    //cache节点
    cacheElement(context.evalNode("cache"));
    //parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    //select|insert|update|delete节点
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }

从上面例子可以看出mapper下面一共有以下几种节点:

  • cache-ref
  • cache
  • parameterMap
  • resultMap
  • sql
  • select
  • insert
  • update
  • delete

每个节点的解析过程请看具体的某一个方法,这里不做说明。


原创文章,转载请注明: 转载自JavaChen Blog,作者:JavaChen
本文链接地址:http://blog.javachen.com/2016/04/21/how-to-parse-mybatis-configuration.html
本文基于署名2.5中国大陆许可协议发布,欢迎转载、演绎或用于商业目的,但是必须保留本文署名和文章链接。 如您有任何疑问或者授权方面的协商,请邮件联系我。