第4篇 mapper.xml文件的定位加载

1.配置解析入口方法

XMLConfigBuilder#parseConfiguration
mappers文件解析的过程,就是<mappers>标签的解析。
从代码中可以知道,mapperElement(root.evalNode("mappers"))方法做的mapper文件解析。

// XMLConfigBuilder
  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"));
      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);
    }
  }

2.mapper解析入口方法

XMLConfigBuilder#mapperElement
mapper解析过程我理解成一下几个步骤:
1.文件定位、载入
2.文件解析(本篇文章不做分析)
1) xml标签解析,例如<mapper><sql><select>等
2) 属性解析,例如resultMap,parameterType等

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 第1种定位方式:将包内的映射器接口实现全部注册为映射器
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 第3种定位方式:使用相对于类路径的资源引用
          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) {
           // 第4种定位方式:使用完全URL
            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) {
          // 第2种定位方式:使用映射器接口实现类的完全限定类名
            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.");
          }
        }
      }
    }
  }

2.1 mapper文件4种定位方式

1和2都是基于类定位的,3和4是基于xml文件地址定位的。

1.将包内的映射器接口实现全部注册为映射器

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

2.使用映射器接口实现类的完全限定类名

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

3.使用相对于类路径的资源引用

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

4.使用完全限定资源定位符(URL)

<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

2.2 mapper文件定位

package和class这两种方式会多一步xml文件地址的定位;
1.接口注册
2.定位xml文件地址(根据接口找到xml文件)
3.解析xml
resource和url这两种方式直接就定位xml地址了。
1.解析xml(该方法会检测mapper接口是否注册了,没有就会重新注册)
2.注册接口(根据xml文件找到class接口)
mybatis通过VFS相关类来读取本地文件系统的。
由此可以看出,mapper文件的解析核心过程就两个:接口注册、解析xml,只不过顺序不一样。我们按照接口注册、解析xm的过程来分析。

2.3 接口注册

MapperProxyFactory包装了接口类,然后注册到knownMappers这个map集合中去。创建接口代理类的时候,会用到MapperProxyFactory,就是从这个集合中去寻找。

public void addMappers(String packageName); // configuration
/**
*mapperRegistry
*/
public void addMappers(String packageName);
public void addMappers(String packageName, Class<?> superType);
public <T> void addMapper(Class<T> type)
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 循环包下面的接口,然后添加
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

// 添加mapper接口到knownMappers中,
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

2.4 mapper.xml文件加载和解析

XMLMapperBuilder 负责解析

/**
* MapperAnnotationBuilder
* package和class会走这个方法
*/
  private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

/**
* XMLMapperBuilder
* 最终都会执行该方法
*/
 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

总结:

1.基于接口的解析,调用的是configuration.addMapper(mapperInterface),先注册接口,然后在解析xml。
2.基于文件定位的解析,调用的是XMLMapperBuilder.parse(),先解析xml,然后在注册接口。
本篇文章只是对mapper.xml文件解析过程做了分析,其中的具体元素标签解析属于细节分析,我会在写一篇文章分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容