• [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() 方法。具体方法如下:

      /**
       * 加载多个开发者自建的 XXXDao.xml 资源。
       *
       * @return 所有 XXXDao 的全限定名 与 当前接口所对应 XXXDao.xml 解析后的全部信息
       */
      @TimeLog(name = "xml-load", explain = "mappers xml load completed : {} ms")
      public Map load() {
        Map 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 模型类的具体实现,代码如下:

      /**
       * 解析 单个开发者自定义的 XXXDao.xml 文件
       *
       * @param resource 单个 XXXDao.xml 的资源文件
       * @return 单个 XXXDao 的全限定名 与 当前接口所对应 XXXDao.xml 解析后的全部信息
       * @throws IOException 读取xml时产生的io异常
       */
      public Map parseClassModel(Resource resource)
          throws IOException, NoSuchMethodException {
        Map 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 nodes = element.childNodes();
          // 便历子节点,解析获取 MethodModel
          Map 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 标签属性的值,与模型类中的属性进行一个匹配并且赋值的过程。具体代码查看如下:

      /**
       * 将 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 文件中方法标签,并映射到方法模型类中的具体实现了。具体代码如下:

      /**
       * 解析 一个 XXXDao 的多个方法。
       *
       * @param nodes   XXXDao.xml 中 <mapper> 下的子标签。即方法标签。
       * @return 返回当前XXXDao类的所有方法信息Map,k: 方法名,v:方法模型(即 xml 里一个方法标签的全部信息)
       */
      private Map parseMethodModel(ClassModel cm, List nodes)
          throws NoSuchMethodException {
        Class namespace = cm.getNamespace();
        Map methods = new HashMap<>();
        List 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 标签节点的方法很简单,就是获取标签中的文本内容返回:

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

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

      /**
       * 解析 <mapper> 下的一个子标签,形成方法模型。
       * 

    * @param node <mapper> 子标签 * @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 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()

      /**
       * 加载基类接口所需 nGQL 模板
       *
       * @return 基类接口方法名 与 nGQL 模板的 Map
       */
      public Map 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 parse(Resource resource) throws IOException {
        Document doc = Jsoup.parse(resource.getInputStream(), "UTF-8", "http://example.com/");
        Map 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 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 处理使用。


    __EOF__

  • 本文作者: knqiufan
  • 本文链接: https://www.cnblogs.com/knqiufan/p/18010817
  • 关于博主: 这世界没有女孩子真的不会转! —— 来自一名代号65的技术宅
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    AI赋能写作:AI大模型高效写作一本通
    vue中同一个页面参数不同动态改变数据与标题
    自学黑客【网络安全】,一般人我劝你还是算了吧
    经典题记录 字符串相加/相乘
    class的get和set
    ROPgadget初识 ——— ret2syscall
    Redis(5)----浅谈压缩列表
    环保行业智能供应链系统加快企业数字化转型,增强企业核心竞争力
    @wufengteam/core 统一中心注册器
    数据治理实战——翼支付金融板块业务数仓建设和数据治理之路
  • 原文地址:https://www.cnblogs.com/knqiufan/p/18010817