mybatis源码解析之mapper解析(二)

mybatis源码解析之mapper解析

xml解析的入口

在SqlSessionFactoryBuilder中存在这样一个方法,所有的的build都会调用下面的这个方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //构建XMLConfigBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse() 将输入流转换为Configuration对象
      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.
      }
    }
  }
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 此处的去解析Configuration配置的代码
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

由上面的代码我们可以知道是由XMLConfigBuilder去解析XML文件,并生成最终的Configuration对象的。
我们再进入上方的函数,会进入到对于xml配置的根(configuration)的解析。也就是对于一https://blog.csdn.net/liu20111590/article/details/81293649 中的mybatis.xml文件中的所有内容的解析。此时我们可以看到最后一句话,是对于mapper的解析。

private void parseConfiguration(XNode root) {
    try {
      //对于properties内容的解析
      propertiesElement(root.evalNode("properties"));
      // 对于Settings配置的解析
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      // 对于typeAliases配置的解析
      typeAliasesElement(root.evalNode("typeAliases"));
      // 对于plugins的解析
      pluginElement(root.evalNode("plugins"));
      // 对于objectFactory的解析
      objectFactoryElement(root.evalNode("objectFactory"));
      // 对于objectWrapperFactory的解析
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 对于reflectorFactory的解析
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 设置setting的值
      settingsElement(settings);
      // 对于environments的解析
      environmentsElement(root.evalNode("environments"));
      // 对于databaseIdProvider的解析
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 对于typeHandler的解析
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 对于mapper的解析
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

mapper的解析

<!--对应的映射-->
    <mappers>
        <!--扫描某一个包-->
        <!--<package name="com.liubin.study.mybatis.object"/>-->
        <!--扫描某一个类-->
        <!--<mapper class="com.liubin.study.mybatis.object.ISupplierLabelDAO"/>-->
        <!--扫描某一个url-->
        <mapper url=""/>
        <!--扫描某个mapper文件-->
        <mapper resource="mapper/supplier-label-mapper.xml"/>
    </mappers>

我们可以看到对于mapper的用法有四种,

  • 扫描一个包
    在扫描包的时候,会扫描包下面的所有的接口。同时会扫描接口相同路径、相同名字的xml文件。
  • 扫描一个接口
    扫描相同接口,同时会扫描接口相同路径、相同名字的xml文件。
  • 扫描一个url
  • 扫描一个xml文件
    扫描对应的xml文件
    接下来我们继续深入函数,看一下mybatis中对于这四种方式的解析
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");
          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.");
          }
        }
      }
    }
  }

由上方的四种解析方式中,分为了两种,一种是调用Configuration,addMapper访问,一种是构建XMLMapperBuiler,然后来进行mapper的解析。接下来对于这两种进行一下解析。

Configuration.addMapper

进入到Configuration类中,我们可以看到三个方法

  /**
   * 将指定包下面的指定接口进行加载
   */
  public void addMappers(String packageName, Class<?> superType) {
    mapperRegistry.addMappers(packageName, superType);
  }

  /**
   * 加载指定包下面的所有接口
   */
  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

  /**
   * 加载指定的接口
   */
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

由这三种我们可以看到对于mapper的加载是委托给mapperRegistry来进行的,我们可以来看一下默认的mapperRegistry

public class MapperRegistry {
  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  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));
        // 创建一个MapperAnnotationBuilder来对xml或者类进行解析
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class<?> superType) {
    // 构建解析过滤器
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    // 过滤查找packageName下面所有的superType类的子类或者实现类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 将查找到的所有类进行添加
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  } 
}

我们深入代码,会发现,对于包下面的所有接口、包下面指定的接口、指定的接口解析都委托给了MapperAnnotationBuilder类去进行解析

MapperAnnotationBuilder 解析mapper

MapperAnnotationBuilder,根据名字我们可以猜测,是用来解析和mapper相关的注解的。对于注解的解析,我们这期不关注。我们只关注从xml文件中载入和mapper相关的内容。

String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 载入xml文件的内容
      loadXmlResource();
      configuration.addLoadedResource(resource);
      // 解析注解的内容
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();

从上述的流程中,我们可以看到一个loadXmlResource()方法,我们继续进入到这个方法。

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

我们可以看到,对于Mapper的解析委托给了XmlMapperBuilder类。而在之前根据资源加载mapper的时候,也是委托给了XmlMapperBuilder来进行解析。由此我们可以确定对于mybatis中的mapper.xml都是交由XmlMapperBuilder来进行解析。

猜你喜欢

转载自blog.csdn.net/liu20111590/article/details/81294270