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 处理使用。

[Ngbatis源码学习] Ngbatis 源码学习之资源加载器 DaoResourceLoader的更多相关文章

  1. libgdx学习记录16——资源加载器AssetManager

    AssetManager用于对游戏中的资源进行加载.当游戏中资源(图片.背景音乐等)较大时,加载时会需要较长时间,可能会阻塞渲染线程,使用AssetManager可以解决此类问题. 主要优点: 1. ...

  2. Spring源码剖析2:Spring IOC容器的加载过程

    spring ioc 容器的加载流程 1.目标:熟练使用spring,并分析其源码,了解其中的思想.这篇主要介绍spring ioc 容器的加载 2.前提条件:会使用debug 3.源码分析方法:In ...

  3. Spring源码剖析3:Spring IOC容器的加载过程

    本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...

  4. MyBatis 源码篇-资源加载

    本章主要描述 MyBatis 资源加载模块中的 ClassLoaderWrapper 类和 Java 加载配置文件的三种方式. ClassLoaderWrapper 上一章的案例,使用 org.apa ...

  5. <JVM中篇:字节码与类的加载篇>04-再谈类的加载器

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  6. 通过源码浅析Java中的资源加载

    前提 最近在做一个基础组件项目刚好需要用到JDK中的资源加载,这里说到的资源包括类文件和其他静态资源,刚好需要重新补充一下类加载器和资源加载的相关知识,整理成一篇文章. 理解类的工作原理 这一节主要分 ...

  7. Away3D引擎学习笔记(一)资源加载解析块

    前文:Away3D断断续续用了一段时间了,三维相关的很多算法,计算转换还是有点绕,整理些自己觉得还有点意思东西,希望大家有用. 三维开始,Away3D构架你场景那几行代码各处都有,这里就不copy了, ...

  8. 驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址

    驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址 最近重新看了乾龙_Heron的<ARM 上电启动及 Uboot 代码分析>(下简称<代码分析>) ...

  9. Duilib学习笔记《07》— 资源加载

    Duilib的界面表现力能如此丰富,很大程度上得益于贴图描述的简单强大.通过之前的学习及参看相关例子,我们可以发现,在XML布局文件中,不管是窗体背景还是控件,都添加了对应的图片资源以此来美化界面.而 ...

  10. 源码学习:一个express().get方法的加载与调用

    刚刚接触express,它的中间件确实把我搞得头晕.get的回调中要不要加next?不加载还会执行下一个中间件么?给get指定'/'路径是不是所有以'/'开头的访问在没有确切匹配时都能执行?use件又 ...

随机推荐

  1. mongoose学习记录

    1 const mongoose = require('mongoose'); 2 3 mongoose.connect('mongodb://localhost/playground') 4 .th ...

  2. Java面试——VUE2&VUE3概览

    一.VUE2.0 1.对于MVVM的理解 MVVM 是 Model-View-ViewModel 的缩写. Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑: View 代表U ...

  3. netstat 命令查看端口状态详解

    转载请注明出处: netstat 可以查看服务器当前端口列表及指定端口的连接状态等: -t : 指明显示TCP端口,t是TCP的首字母. -u : 指明显示UDP端口,u是UDP的首字母 -p : 显 ...

  4. [转帖]看看 Jmeter 是如何玩转 redis 数据库的

    柠檬小欧 2021-08-31 20:06420 Jmeter 作为当前非常受欢迎的接口测试和性能测试的工具,在企业中得到非常广泛的使用,而 Redis 作为缓存数据库,也在企业中得到普遍使用,那如何 ...

  5. kafka学习之五_多个磁盘的性能验证

    kafka学习之五_多个磁盘的性能验证 背景 周末在家学习kafka 上午验证了grafana+kafka_exporter的监控 下午想着验证一把性能相关. kafka学习之三里面,有成套的脚本. ...

  6. [转帖]021系统状态检测命令sosreport

    https://www.cnblogs.com/anyoneofus/p/16467677.html   sosreport命令用于收集系统配置及架构信息并输出诊断文档.

  7. [转帖] Linux命令拾遗-查看系统信息

    https://www.cnblogs.com/codelogs/p/16060714.html 简介# 作为一名程序员,有时需要关注自己的进程运行在什么样的软硬件环境里,比如几核cpu.固态硬盘还是 ...

  8. 【转帖】Java Full GC (Ergonomics) 的排查

    文章目录 1. Full GC (Ergonomics) 1.1 Java 进程一直进行 Full GC 1.2 Full GC 的原因 1.3 检查堆占用 2. 代码检查 3. 解决方式 1. Fu ...

  9. [转帖]nginx性能和软中断

    https://plantegg.github.io/2022/11/04/nginx%E6%80%A7%E8%83%BD%E5%92%8C%E8%BD%AF%E4%B8%AD%E6%96%AD/ n ...

  10. [转帖]一文解决内核是如何给容器中的进程分配CPU资源的?

    https://zhuanlan.zhihu.com/p/615570804   现在很多公司的服务都是跑在容器下,我来问几个容器 CPU 相关的问题,看大家对天天在用的技术是否熟悉. 容器中的核是真 ...