[Ngbatis源码学习] Ngbatis 源码阅读之资源加载器 DaoResourceLoader

Ngbatis 源码阅读之资源加载器 DaoResourceLoader

DaoResourceLoaderNgbatis 的资源文件加载器,扩展自 MapperResourceLoader。本篇文章主要分析这两个类。

1. 相关类

  • MapperResourceLoader
  • DaoResourceLoader

2. MapperResourceLoader

在介绍 DaoResourceLoader 之前有必要先介绍一下 MapperResourceLoaderDaoResourceLoaderMapperResourceLoader 的扩展。

MapperResourceLoader 继承了 PathMatchingResourcePatternResolver 类,关于 PathMatchingResourcePatternResolver 的有关内容,可以查看《Ngbatis源码学习之 Spring 资源管理 ResourceLoader》这篇文章。

2.1. load

MapperResourceLoader 的作用是加载解析开发人员自定义的 XML 文件资源 ,核心是 load() 方法。具体方法如下:

java 复制代码
  /**
   * 加载多个开发者自建的 XXXDao.xml 资源。
   *
   * @return 所有 XXXDao 的全限定名 与 当前接口所对应 XXXDao.xml 解析后的全部信息
   */
  @TimeLog(name = "xml-load", explain = "mappers xml load completed : {} ms")
  public Map<String, ClassModel> load() {
    Map<String, ClassModel> resultClassModel = new HashMap<>();
    try {
      // 加载 Resource 资源
      Resource[] resources = getResources(parseConfig.getMapperLocations());
      // 遍历资源并逐一解析
      for (Resource resource : resources) {
        resultClassModel.putAll(parseClassModel(resource));
      }
    } catch (IOException | NoSuchMethodException e) {
      throw new ResourceLoadException(e);
    }
    // 返回解析 xml 后的全部信息
    return resultClassModel;
  }

可以看到在 load() 方法中首先调用 PathMatchingResourcePatternResolver 类的 getResources 方法加载指定文件夹位置下的所有 xml 文件,再对加载的 Resource 资源数组进行遍历,逐一对内容进行解析映射为模型类返回。

重点在 parseClassModel 方法。

2.2. parseClassModel

parseClassModel 方法是解析 xml 文件,将 xml 内容映射到 ClassModel 模型类的具体实现,代码如下:

java 复制代码
  /**
   * 解析 单个开发者自定义的 XXXDao.xml 文件
   *
   * @param resource 单个 XXXDao.xml 的资源文件
   * @return 单个 XXXDao 的全限定名 与 当前接口所对应 XXXDao.xml 解析后的全部信息
   * @throws IOException 读取xml时产生的io异常
   */
  public Map<String, ClassModel> parseClassModel(Resource resource)
      throws IOException, NoSuchMethodException {
    Map<String, ClassModel> result = new HashMap<>();
    // 从资源中获取文件信息,使用 Jsoup 进行 IO 读取
    Document doc = Jsoup.parse(resource.getInputStream(), "UTF-8", "http://example.com/");
    // 传入 xml 解析器,获取 xml 信息
    Elements elementsByTag = doc.getElementsByTag(parseConfig.getMapper());

    for (Element element : elementsByTag) {
      ClassModel cm = new ClassModel();
      cm.setResource(resource);
      // 解析标签,获取 namespace 的值
      match(cm, element, "namespace", parseConfig.getNamespace());
      // 解析标签,获取 space 的值
      match(cm, element, "space", parseConfig.getSpace());

      // 如果标签中未设置 space,则从注解获取 space
      if (null == cm.getSpace()) {
        setClassModelBySpaceAnnotation(cm);
      }
      // 将需要初始化的空间名添加到列表并在 sessionPool 中,初始化 session.
      addSpaceToSessionPool(cm.getSpace());

      // 获取子节点(方法配置)
      List<Node> nodes = element.childNodes();
      // 便历子节点,解析获取 MethodModel
      Map<String, MethodModel> methods = parseMethodModel(cm, nodes);
      cm.setMethods(methods);
      // 将结果和加入到映射缓存,key 值为代理类名称。
      result.put(cm.getNamespace().getName() + PROXY_SUFFIX, cm);
    }
    return result;
  }

可以看到这个方法中解析 xml 主要分为以下几个步骤:

  • 使用 Jsoup 的方式加载 Resource 并传入 xml 解析器,从中获取 xml 信息
  • 遍历 Elements,获取到 namespace(全限定类名)和 space(图空间名称)的值,加入 ClassModel 模型类。若 space 的值未在 xml 中设置,则直接从对应 Dap 中设置的实体类中的注解里获取 space。当然,也可能为空。
  • 判断在配置文件中是否开启了 sessionPool 会话池,如果有则加入 space 列表,用于初始化 session。
  • 继续使用 Jsoup 的方法获取 xml 子节点的数据,这边的子节点就是对应的方法配置了。
  • 遍历子节点,在 parseMethodModel 方法来中解析 xml,并映射到 MethodModel 模型类中。
  • 将解析好的 ClassModel 加入到 Map 中,key 值为之后要创建的代理类名称。

所以总结下说这个方法就是加载 Resource,解析 xml,并映射为模型类,与代理类名称一一对应并返回供之后使用。

这个方法又涉及到了很多的具体的解析方法,重点查看 match 方法和 parseMethodModel 方法。

2.3. match

match 方法其实就是获取 xml 标签属性的值,与模型类中的属性进行一个匹配并且赋值的过程。具体代码查看如下:

java 复制代码
  /**
   * 将 xml 中的标签属性及文本,与模型进行匹配并设值。(模型包含 类模型与方法模型)
   *
   * @param model  ClassModel 实例或 MethodModel 实例
   * @param node   当前 xml 单个 gql 的xml节点
   * @param javaAttr 欲填入 model 的属性名
   * @param attr   node 标签中的属性名
   */
  private void match(Object model, Node node, String javaAttr, String attr) {
    String attrTemp = null;
    try {
      String attrText = node.attr(attr);
      if (isBlank(attrText)) {
        return;
      }
      attrTemp = attrText;
      Field field = model.getClass().getDeclaredField(javaAttr);
      Class<?> type = field.getType();
      Object value = castValue(attrText, type);
      ReflectUtil.setValue(model, field, value);
    } catch (ClassNotFoundException e) {
      throw new ParseException("类型 " + attrTemp + " 未找到");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

代码其实很简单,传入模型类、node 标签、需要设置的模型类属性名、node 标签需要获取值的属性名这四个参数,获取 node 标签属性值之后,使用反射将属性值赋值给模型类对应的属性里去。

2.4. parseMethodModel

parseMethodModel 方法就是解析 xml 文件中方法标签,并映射到方法模型类中的具体实现了。具体代码如下:

java 复制代码
  /**
   * 解析 一个 XXXDao 的多个方法。
   *
   * @param nodes   XXXDao.xml 中 &lt;mapper&gt; 下的子标签。即方法标签。
   * @return 返回当前XXXDao类的所有方法信息Map,k: 方法名,v:方法模型(即 xml 里一个方法标签的全部信息)
   */
  private Map<String, MethodModel> parseMethodModel(ClassModel cm, List<Node> nodes)
      throws NoSuchMethodException {
    Class namespace = cm.getNamespace();
    Map<String, MethodModel> methods = new HashMap<>();
    List<String> methodNames = getMethodNames(nodes);
    for (Node methodNode : nodes) {
      if (methodNode instanceof Element) {
        // nGQL 为自定义查询语句,若存在 nGQL 标签,则执行 parseNgqlModel 方法对标签进行解析
        if (((Element) methodNode).tagName().equalsIgnoreCase("nGQL")) {
          if (Objects.isNull(cm.getNgqls())) {
            cm.setNgqls(new HashMap<>());
          }
          // 解析 nGQL 语句,并映射到对应模型类
          NgqlModel ngqlModel = parseNgqlModel((Element) methodNode);
          cm.getNgqls().put(ngqlModel.getId(),ngqlModel);
        } else {
          // 解析 node 标签内容,并映射为 MethodModel 方法
          MethodModel methodModel = parseMethodModel(methodNode);
          // 将需要初始化的空间名添加到列表并在 sessionPool 中,初始化 session.
          addSpaceToSessionPool(methodModel.getSpace());
          // 根据方法名,利用反射获取唯一的方法
          Method method = getNameUniqueMethod(namespace, methodModel.getId());
          methodModel.setMethod(method);
          Assert.notNull(method,
            "接口 " + namespace.getName() + " 中,未声明 xml 中的出现的方法:" + methodModel.getId());
          // 返回类型检查
          checkReturnType(method, namespace);
          // 对接口进行分页支持
          pageSupport(method, methodModel, methodNames, methods, namespace);
          // 将解析结果加入到 Map 中
          methods.put(methodModel.getId(), methodModel);
        }
      }
    }
    return methods;
  }

可以看到在这个方法中,首先会判断 node 节点元素是否含有 nGQL 标签,如果有则解析 nGQL 语句并映射到 NgqlModel 自定义 nGQL 语句的模型类。解析 nGQL 标签节点的方法很简单,就是获取标签中的文本内容返回:

java 复制代码
  protected NgqlModel parseNgqlModel(Element ngqlEl) {
    // 获取元素中的 id 和文本内容
    return  new NgqlModel(ngqlEl.id(),ngqlEl.text());
  }

如果没有 nGQL 标签,则调用 parseMethodModel 方法解析 node 节点元素,并映射为 MethodModel 方法模型。这个方法也很简单,在方法内部同样是调用了 match 来进行解析,前面已经描述过 match 的用法,不再赘述。

java 复制代码
  /**
   * 解析 &lt;mapper&gt; 下的一个子标签,形成方法模型。
   * <p/>
   * @param node &lt;mapper&gt;  子标签
   * @return 方法模型
   */
  protected MethodModel parseMethodModel(Node node) {
    MethodModel model = new MethodModel();
    match(model, node, "id", parseConfig.getId());
    match(model, node, "parameterType", parseConfig.getParameterType());
    match(model, node, "resultType", parseConfig.getResultType());
    match(model, node, "space", parseConfig.getSpace());
    match(model, node, "spaceFromParam", parseConfig.getSpaceFromParam());

    List<Node> nodes = node.childNodes();
    model.setText(nodesToString(nodes));
    return model;
  }

映射处理完成之后,会再进行一些后置处理工作,包括返回类型的检查、对方法的分页支持等操作,加入 Map 后返回。

所以将 MapperResourceLoader 类的代码梳理下来能知道,它的作用就是解析 xml 的文件内容,并将其映射为模型类。

3. DaoResourceLoader

在 Ngbatis 内部包含了一个基础操作和内置预定义操作的 xml,会在启动时就被加载解析,作用是为开发人员提供不需要再次编写可直接使用的图库操作。而在 DaoResourceLoader 中就做了这件事情。

DaoResourceLoader 继承了 MapperResourceLoader,所以在了解了 MapperResourceLoader 的作用之后,DaoResourceLoader 类的内容就很好理解了,就是在 MapperResourceLoader 的基础上又扩展了一个加载基类接口所需要的 xml 文件的模板方法。

做法与 MapperResourceLoader 类中的加载方式类似,同样是通过调用 getResource 方法加载指定的 xml,并对 xml 内容进行解析返回。重点方法是 loadTpl()

java 复制代码
  /**
   * 加载基类接口所需 nGQL 模板
   *
   * @return 基类接口方法名 与 nGQL 模板的 Map
   */
  public Map<String, String> loadTpl() {
    try {
      Resource resource = getResource(parseConfig.getMapperTplLocation());
      return parse(resource);
    } catch (IOException e) {
      throw new ResourceLoadException(e);
    }
  }

  /**
   * 资源文件解析方法。用于获取 基类方法与nGQL模板
   *
   * @param resource 资源文件
   * @return 基类接口方法名 与 nGQL 模板的 Map
   * @throws IOException 可能找不到 xml 文件的 io 异常
   */
  private Map<String, String> parse(Resource resource) throws IOException {
    Document doc = Jsoup.parse(resource.getInputStream(), "UTF-8", "http://example.com/");
    Map<String, String> result = new HashMap<>();
    // 获取基类 NebulaDaoBasic 的所有方法
    Method[] methods = NebulaDaoBasic.class.getMethods();
    // 遍历方法,并与 xml 文件中的方法名一一对应,解析返回
    for (Method method : methods) {
      String name = method.getName();
      Element elementById = doc.getElementById(name);
      if (elementById != null) {
        List<TextNode> textNodes = elementById.textNodes();
        // 获取 xml 文件中的文本内容
        String tpl = nodesToString(textNodes);
        // key 为方法名,value 为 xml 文件中标签内的文本内容
        result.put(name, tpl);
      }
    }
    return result;
  }
}

可以看到在 loadTpl 中,获取了 NebulaDaoBasic 基类的所有方法,并通过方法名找到 xml 与之对应的 node 标签,获取到文本内容并加入到 Map 返回。

4. 总结

总结一下,DaoResourceLoader 就是加载解析 xml 文件的资源加载器,包括加载解析自定义的 xml 文件和 NebulaDaoBasic 基类所需的基础 xml,将 xml 文件映射为模型类供之后的 Bean 处理使用。

相关推荐
小钊(求职中)4 小时前
Java开发实习面试笔试题(含答案)
java·开发语言·spring boot·spring·面试·tomcat·maven
拾忆,想起8 小时前
Spring 和 Spring MVC 的关系是什么?
java·spring boot·spring·spring cloud·微服务
best_virtuoso10 小时前
Mybatis MyBatis框架的缓存 一级缓存
spring·缓存·mybatis
逸狼12 小时前
【JavaEE进阶】Spring MVC(2)
spring·java-ee·mvc
m0_7482338813 小时前
使用 Logback 的最佳实践:`logback.xml` 与 `logback-spring.xml` 的区别与用法
xml·spring·logback
快乐就好ya13 小时前
Dfs分布式文件存储
java·spring boot·分布式·后端·spring·spring cloud
hong_zc15 小时前
Spring IoC
java·后端·spring
zhyhgx16 小时前
【Spring】详解Spring IOC&DI
java·spring boot·后端·spring·java-ee·intellij-idea
指尖下的技术17 小时前
SpringCloud面试题----为什么会产生Eureka的自我保护, 如何关闭自我保护机制
spring·spring cloud·eureka
web135085886351 天前
【Spring Boot】Spring AOP动态代理,以及静态代理
spring boot·后端·spring