Java Gradle项目中的资源正确获取方式
引言
一个java gradle项目会涉及到资源的访问. 一般情况下会将当前项目所需的资源文件全部放置于resources文件夹下, 无论是main文件下的source code 还是test文件夹下的test code.
都或多或少的涉及到获取resources文件夹下的资源. 本文主要目的就是详细的总结一下如何获取resources文件夹下的资源.
两个getresource方法
来看一个简单的java gradle项目(称呼其为simpleresource)的项目结构
首先这个project执行build之后会在根目录下创建一个out目录, 这个目录存放所有的编译结果(class文件以及资源文件). 如上图所示production文件夹对应的是source code而test文件夹对应的是test code.
所有的资源都会存储在resources文件夹内部. 当程序运行时获取的资源就是这个resources文件夹下的资源.
我们使用最多的获取资源的方法有两个 class.getresource 和 classloader.getresource 但是这两个方法传递参数与结果不同, 下面详细分析一下这两个方法参数以及返回值.
先看 classloader 中的 getresource 方法. 只需要获取类加载器对象即可(获取方式不再赘述). 先看这个方法的api文档相关的描述:
finds the resource with the given name. a resource is some data (images, audio, text, etc) that can be accessed by class code in a way that is independent of the location of the code. the name of a resource is a '/'-separated path name that identifies the resource.
this method will first search the parent class loader for the resource; if the parent is null the path of the class loader built-in to the virtual machine is searched. that failing, this method will invoke findresource(string) to find the resource.
从这个描述中可以得知提供资源的路径(我理解的是相对路径), 正常情况下该方法会返回资源完整的url. 传递的参数有一个重要的注意事项, 就是传递的参数不能够以/ 开始, 这也是我为什么称呼这个参数为资源的相路径. 举个例子
url test = this.getclass().getclassloader().getresource("/");
运行上述代码返回的结果是:
参考simpleresource的项目结构, 正确获取 com.mainres 下的文件的正确做法是:
string name = "com/mainres/testmain.txt";
url test = this.getclass().getclassloader().getresource(name);
结果为:
如果在表示资源路径的字符串中加上 / 那么获取到的url依然为null
string name = "/com/mainres/testmain.txt";
url test = this.getclass().getclassloader().getresource(name);
宗上所述, 使用类加载器获取资源的方式传递的参数为资源相对路径(相对于resources文件夹的路径), 既然是相对路径自然参数 不能够以 / 开始.
有一个问题需要注意, 当传递参数为空字符串的时候, 得到路径其实是classes文件夹的绝对路径, 但当传递一个正确的资源路径相对字符串时, 得到路径却是resources文件夹下的资源路径.
string name = "";
url test = this.getclass().getclassloader().getresource(name);
我的理解是本质上是通过此方法获取的其实类加载器加载的class字节码目录, 所以使用空字符串会看到实际输出的是classes文件夹绝对路径, 当传递正确的资源路径的时候, 代码层面做转换, 转而获取与classes文件夹同级的resources文件夹下的资源.
再看 class 中的 getresurce 方法
由于这个方法传递参数是否是以 / 开头会产生不同的结果, 且使用这个方法也比较容易和 classloader 中的 getresource 方法搞混淆, 所以本文多次强调传递的参数是否以 / 开始.
首先看传递参数为 "" 和 / 的两种情况得到的结果:
使用空字符串:
string name = "";
url test = this.getclass().getresource(name);
运行结果:
使用 /
string name = "/";
url test = this.getclass().getresource(name);
运行结果为:
最大的区别是使用空字符串 "" 获取的路径是相对于包的目录, 而使用 / 获取的路径是类加载器加载class文件的目录, 这个和 classloader 的 getresource 方法传递 "" 字符串的结果是一样的. 所以如果要正确的获取到资源文件,
那么使用 class 的 getresource 方法如下:
string name = "/com/mainres/testmain.txt";
url test = this.getclass().getresource(name);
运行结果:
所以综上所述, 一个简单的防止两个方法传递参数搞混淆的记忆方式就是使用 class 的 getresource 方法需要加 / 而使用 classloader 的 getresource 方法不要加 /.
其实参考 class 类中的 getresource 方法:
public java.net.url getresource(string name) { name = resolvename(name); classloader cl = getclassloader0(); if (cl==null) { // a system class. return classloader.getsystemresource(name); } return cl.getresource(name); }
本质上讲它也是调用classloader 中的getresource 方法. 其中resolvename 这个方法对传递的参数做了转换.
private string resolvename(string name) { if (name == null) { return name; } if (!name.startswith("/")) { class<?> c = this; while (c.isarray()) { c = c.getcomponenttype(); } string basename = c.getname(); int index = basename.lastindexof('.'); if (index != -1) { name = basename.substring(0, index).replace('.', '/') +"/"+name; } } else { name = name.substring(1); } return name; }
当传递的参数带有/ 时候, resolvename 会将/ 去除后的字符串返回, 最后调用classloader 中的 getresource 方法.
小结
本文对比了一下class 和 classloader 中的getresource 方法的差异,如果单纯从资源的获取角度来看最终调用的都是classloader 中的getresource 方法.
简单记忆即是使用class 的getresource 方法资源路径需要加/ 而使用classloader 中的getresource 方法则不需要加/.
以上这篇java gradle项目中的资源正确获取方式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
下一篇: SpringBoot系列(二)入门知识