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. java进阶(37)--多线程

    文档目录: 一.进程与线程 二.多线程的实现 三.获取线程名与线程对象 四.线程sleep方法 五.线程调度与优先级 六.线程安全(重点) 七.死锁 ------------------------- ...

  2. CS2打开可以听到声音,但黑屏问题?

    1.问题 我这里原先是可以启动CS2的,但是后来在CS2中重新调整了分辨率等等,之后由于某种原因又调整了屏幕分辨率,导致后面一进入CS2登录界面,橙色登陆界面就会缩在左上角一小块,并且之后就会陷入黑屏 ...

  3. [转帖]银河麒麟v10下载(服务器版 桌面版) - 2023-11-14更新

    银河麒麟v10下载(服务器版 桌面版) - 2023-11-14更新 如需转载请标明出处:[http://blog.csdn.net/itas109] 文章目录 银河麒麟v10下载(服务器版 桌面版) ...

  4. [转帖]一文读懂 HugePages(大内存页)的原理

    https://juejin.cn/post/6956541214426398757 在介绍 HugePages 之前,我们先来回顾一下 Linux 下 虚拟内存 与 物理内存 之间的关系. 物理内存 ...

  5. 疯狂GC的第二种处理方式-ChatGPT的学习之四

    疯狂GC的第二种处理方式-ChatGPT的学习之四 摘要 上一个脚本太复杂了. 而且要改启动脚本. 课间休息跟人扯淡聊起来 chatGPT 发现他的语法很有用 但是思路不太对. 不过突然根据文档里写的 ...

  6. [转帖]010 Linux 文本统计与去重 (wc 和 uniq)

    https://my.oschina.net/u/3113381/blog/5427461 wc 命令一般是作为组合命令的一员与其他命令一同起到统计的作用.而一般情况下使用 wc -l 命令较多. u ...

  7. Specjvm2008的简单学习

    Specjvm2008的简单学习 摘要 前期整理过很多需要通过编译指定命令进行性能测试的工具 但是这种工具无法充分模式JAVA应用. 并且无法模拟不同jvm版本的性能情况. 早上去北京出差路上看到了 ...

  8. [转帖]oracle如何删除datafile,误删除Oracle datafile 无法打开数据库

    1.在Oracle open方式下,直接从OS上删除了datafile文件. rm /u02/rmants.dbf 2.数据库关闭后,无法打开数据库,只能到mount状态. SQL> alter ...

  9. Sysbench 开启超线程/关闭超线程以及容器运行数据库的性能损耗

    Sysbench 开启超线程/关闭超线程性能损耗 摘要 Stress-NG 测试完之后 突然想 使用sysbenchen也进行一次压测 验证一把 超线程对数据的性能影响. 压测命令 ./sysbenc ...

  10. awk的简单样例

    shell awk求和 当第一列相同时,对应的第二列相加 awk'{sum[$1]+=$2}END{for(c in sum){print c,sum[c]}}'输入文件名 在Shell中,我们可以用 ...