首页 > 编程语言 >[Ngbatis源码学习] Ngbatis 源码学习之资源加载器 DaoResourceLoader

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

时间:2024-02-07 12:11:06浏览次数:42  
标签:xml node Map 标签 DaoResourceLoader Ngbatis 源码 解析 方法

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<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 模型类的具体实现,代码如下:

  /**
   * 解析 单个开发者自定义的 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 标签属性的值,与模型类中的属性进行一个匹配并且赋值的过程。具体代码查看如下:

  /**
   * 将 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 中 &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 标签节点的方法很简单,就是获取标签中的文本内容返回:

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

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

  /**
   * 解析 &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()

  /**
   * 加载基类接口所需 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 处理使用。

标签:xml,node,Map,标签,DaoResourceLoader,Ngbatis,源码,解析,方法
From: https://www.cnblogs.com/knqiufan/p/18010817

相关文章

  • 通达信跟庄乾坤源码副图
    {股票指标}DLYZ1:=SUM((WINNER(C)*100),30)/30*0.1;DLYZ2:=SUM((WINNER(C)*100),20)/20*0.4;DLYZ3:=SUM((WINNER(C)*100),10)/10*0.3;DLYZ4:=SUM((WINNER(C)*100),5)/5*0.1;DLYZ5:=SUM((WINNER(C)*100),3)/3*0.1;DLYZZ:=DLYZ1+DLYZ2+DLYZ3+DLYZ4+DLYZ5;赚钱效应:IF(DLYZ......
  • 通达信暗流涌动指标公式源码副图
    {股票指标}TYP:=(IF(HIGH<=0,CLOSE,HIGH)+IF(LOW<=0,CLOSE,LOW)+CLOSE)/3;CL:=(TYP-ma(TYP,30))/(0.015*AVEDEV(TYP,30));C2:=MA(CL,4);C3:=MA(CL,10);DRAWBAND(CL,RGB(0,255,0),C2,RGB(0,0,0));DRAWBAND(CL,RGB(255,0,0),C3,RGB(0,0,0));动力:CL,COLORYELLOW,LINET......
  • 通达信MACD买卖副图指标公式源码
    {股票指标}VAR3:=(CLOSE-MA(CLOSE,6))/MA(CLOSE,6)*100; VAR4:=(CLOSE-MA(CLOSE,24))/MA(CLOSE,24)*100;VAR5:=(CLOSE-MA(CLOSE,32))/MA(CLOSE,32)*100;VAR6:=(VAR3+VAR4+VAR5)/3;VAR7:=EMA(VAR6,5);指标:=EMA(EMA(VAR3,5),5)*3,COLORSTICK;VAR8:=IF(VAR6<=-20,10,0......
  • 通达信【龙头战狼】珍藏版套装指标 止盈止损 胜者为王 终极盈利模式 源码文件分享
    {股票指标}【战狼波段主图】本套指标设计两个主图,一个看盘简单明了,还有一个波段主图,操作波段上涨非常好,买卖点明确!【战狼主力资金】副图有拉升资金和主力资金两个信号,两个信号都大于0,而且处于上升趋势,易产生大妖股,可以辅助战狼信号操盘.1、指标原理:本指标主力洗盘+拉升特征......
  • 通达信鼎牛猎杀选股指标公式源码副图
    {股票指标}YM:=Ema(SLOPE((CLOSE+HIGH+LOW)/3,24)*20+(CLOSE+HIGH+LOW)/3,48);macd价格1:=EMA(CLOSE,3)-EMA(CLOSE,9);DEA1021:=EMA(EMA(MacD价格1,102)-EMA(MACD价格1,204),9);DEA511:=EMA(EMA(MACD价格1,51)-EMA(MACD价格1,102),9);DEA36361:=EMA(EMA(MACD价格1,36)-EMA......
  • 通达信暗流涌动源码副图
    {股票指标}TYP:=(IF(HIGH<=0,CLOSE,HIGH)+IF(LOW<=0,CLOSE,LOW)+CLOSE)/3;CL:=(TYP-ma(TYP,30))/(0.015*AVEDEV(TYP,30));C2:=MA(CL,4);C3:=MA(CL,10);DRAWBAND(CL,RGB(0,255,0),C2,RGB(0,0,0));DRAWBAND(CL,RGB(255,0,0),C3,RGB(0,0,0));动力:CL,COLORYELLOW,LINET......
  • 通达信金牛波段暴起源码副图
    {股票指标}AA:=LLV(LOW,34);BB:=HHV(HIGH,30);DD:=(Ema(((CLOSE-AA)/(BB-AA))*(4),4))*(25);谷:=(((DD-LLV(DD,21))/(HHV(DD,21)-LLV(DD,21)))*(4))*(25);GUP1:=(CLOSE-LLV(LOW,36))/(HHV(HIGH,36)-LLV(LOW,36))*100;GUP2:=SMA(GUP1,3,1);GUP3:=SMA(GUP2,3,1);GUP4:=SM......
  • 通达信天天满仓选股公式源码副图
    {股票指标}VAR01:=Ema((CLOSE-LLV(LOW,25))/(HHV(HIGH,25)-LLV(LOW,25))*100,5);VAR02:=(HHV(HIGH,25)-LLV(LOW,25))/EMA(CLOSE-LLV(LOW,25),5);VAR03:=crOSS(VAR02,VAR01);VAR04:=REF(VAR02,2);VAR05:=COUNT(VAR03,7)>0ANDVAR04;底部满仓:=VAR05ANDCOUNT(VAR05,5)=1......
  • 通达信逆流而上指标公式源码副图
    {股票指标}大盘线:INDEXC,NODRAW;DM5:ma(大盘线,5);个股:=EMA(CLOSE,120)/EMA(INDEXC,120);个股线:CLOSE/个股,NODRAW;GM5:MA(个股线,5);TDXstEP110:STICKLINE(大盘线>=REF(大盘线,1),大盘线,REF(大盘线,1),3,0),COLORYELLOW;TDXSTEP111:STICKLINE(大盘线<ref(大盘线,1)......
  • 通达信角度雷达寻牛排序指标公式源码副图
    {股票指标}ma5:=MA(C,5);MA10:=MA(C,10);MA30:=MA(C,30);角度5:ATAN((MA5/REF(MA5,1)-1)*100)*180/3.14159,LINETHICK2,COLORYELLOW;角度10:ATAN((MA10/REF(MA10,1)-1)*100)*180/3.14159,LINETHICK2,COLORMAGENTA;角度30:ATAN((MA30/REF(MA30,1)-1)*100)*180/3.14159,LIN......