您的位置:首页技术文章
文章详情页

Java 正确地从类路径中获取资源

【字号: 日期:2022-08-12 10:56:34浏览:25作者:猪猪
目录Java 可通过以下几种方法来访问资源:Class.getResource 与 ClassLoader.getResource 的区别测试代码打包为 Jar 包后的变化错误与陷阱正确使用 getResource 方法getResources: 枚举资源实例Java 可通过以下几种方法来访问资源: Class 的 getResource 方法 ClassLoader 的 getResource 方法 ClassLoader 的 getSystemResource 静态方法

在使用中,Class 可通过直接引用类的 class 属性而获得,或是通过实例的 getClass() 方法来获得。获取 ClassLoader 的方式则比较多,常见以下几种:

调用 Class 的 getClassLoader 方法,如:getClass().getClassLoader() 由当前线程获取 ClassLoader:Thread.currentThread().getContextClassLoader() 获取系统 ClassLoader: ClassLoader.getSystemClassLoader()

不过,若是对 Java 的 ClassLoader 概念不太了解,最好还是尽量避免使用它。

Class.getResource 与 ClassLoader.getResource 的区别

这两种方式,都接受一个字符串形式的路径表达式,即资源名,并返回找到的资源的 URL。两种方式都可用来定位资源,在网络上流传的文章中,两者都是常见的。实际上,Class 的 getResource 方法也调用了 ClassLoader 的 getResource 方法,但两者有着很大的不同,不了解这两种方法的区别,就容易造成隐患。隐患经常比编写时就出错要可怕得多,因为它在一定场合下是正常的,不容易被发现。

两者最大的区别,是从哪里开始寻找资源。ClassLoader 并不关心当前类的包名路径,它永远以 classpath 为基点来定位资源。而 Class.getResource 则不同,如果资源名是绝对路径(以'/'开头),它会将开头的'/'去除,然后调用 ClassLoader 的 getResource 方法来寻找资源;如果资源名是相对路径,它会在当前的包路径下面寻找资源。

举例来说,假设我们有一个类:test.App (包名为 test),并且在 test 包下有一个与类名同名的 js 文件,名为 App.js。如果用 ClassLoader 来获取这个 js 文件,应该这样写:

App.class.getClassLoader().getResource('test/App.js');

如果用 Class 的 getResource 方法,则有两种写法:

使用相对路径:

App.class.getResource('App.js'); 使用绝对路径:

App.class.getResource('/test/App.js');

从上面的例子,可以看出两者之间巨大的区别。有些人从网络上复制类似的代码,看看不能正确运行,就开始尝试在资源名前加上 '/',或是去掉开头的 '/',试成功了,便算完工,这绝非正道。

Class 与 ClassLoader 的 getResource 方法还有其它一些不同,对 Class 的 getResource 方法来说,若传入的是相对路径,它还会尝试做包名与路径名的转换。查看 Class.getResource 方法的源码,可以看到它首先对资源名调用了 resolveName 方法,然后再调用 ClassLoader 的 getResource 方法来完成资源的定位。

测试代码

作为演示,我写了以下代码来展示 Class 与 ClassLoader 的 getResource 方法的输出:

/** * Copyright (c) 2014 Chen Zhiqiang <chenzhiqiang@mail.com>. Released under the MIT license. */package test;import java.net.URL;import java.util.Enumeration;/** * Tests for the use of {@link Class#getResource(String)} and * {@link ClassLoader#getResource(String)}. * * @author Chen Zhiqiang <chenzhiqiang@mail.com> */public class ClassResourceTest { Class<ClassResourceTest> cls = ClassResourceTest.class; ClassLoader ldr = cls.getClassLoader(); // Thread.currentThread().getContextClassLoader() public static void println(Object s) {System.out.println(s); } void showResource(String name) {println('## Test resource for: “' + name + '” ##');println(String.format('ClassLoader#getResource('%s')=%s', name, ldr.getResource(name)));println(String.format('Class#getResource('%s')=%s', name, cls.getResource(name))); } public final void testForResource() throws Exception {showResource('');showResource('/');showResource(cls.getSimpleName() + '.class');String n = cls.getName().replace(’.’, ’/’) + '.class';showResource(n);showResource('/' + n);showResource('java/lang/Object.class');showResource('/java/lang/Object.class'); } public static void main(String[] args) throws Exception {println('java.class.path: ' + System.getProperty('java.class.path'));println('user.dir: ' + System.getProperty('user.dir'));println('');ClassResourceTest t = new ClassResourceTest();t.testForResource(); }}

编译上述代码,看看不同资源路径的输出结果。

打包为 Jar 包后的变化

现在,将上述代码编译后的结果打包成 Jar 文件,假设是 test.jar ,然后从这个 jar 包中运行上述代码,再看看输出结果,比较下与上面的输出有什么变化:

java -classpath test.jar test.ClassResourceTest

值得注意的几点:

Class.getResource('') 还有其它一些输出,结果是 jar:file:/some_path/test.jar!/some_path,而在打包为 Jar 之前,它们的输出形式是 file:/some_path...; Class.getResource('/') 为 null,而在打包之前,该输出是 ClassResourceTest 的类路径; ClassLoader.getResource('') 为 null,而在打包之前,该输出是 ClassResourceTest 的类路径; 调用 ClassLoader.getResource 方法时,若资源名为绝对路径,不管是否打包,其输出结果为 null,至少在我这里是这样。 错误与陷阱 使用 Class.getResource('/') 或 ClassLoader.getResource('') 来当作类路径的根。

这是一种常见的错误,并在网络上广为流传。它们在打包成 Jar 包后,其结果会发生变化。

获得 getResource 方法的输出后,简单地对结果调用 getFile 或 getPath,并把它当作文件路径来处理。

资源有可能以文件和目录的形式位于类路径之中,但也可能打包进了 Jar 包或 Zip 包,你不能假设你的代码不会被打包。

将绝对路径传给 ClassLoader 的 getResource 方法。

网络上有人说,对于 ClassLoader 的 getResource 方法来说,资源名是否以 '/' 开头是一样的,然而,在我这里,ClassLoader 的 getResource 方法并不接受绝对路径,其输出结果为 null。

正确使用 getResource 方法 避免使用 Class.getResource('/') 或 ClassLoader.getResource('')。你应该传入一个确切的资源名,然后对输出结果作计算。比如,如果你确实想获取当前类是从哪个类路径起点上执行的,以前面提到的 test.App 来说,可以调用 App.class.getResource(App.class.getSimpleName() + '.class')。如果所得结果不是 jar 协议的URL,说明 class 文件没有打包,将所得结果去除尾部的 'test/App.class',即可获得 test.App 的类路径的起点;如果结果是 jar 协议的 URL,去除尾部的 '!/test/App.class',和前面的 'jar:',即是 test.App 所在的 jar 文件的 url。 如果要定位与某个类同一个包的资源,尽量使用那个类的getResource方法并使用相对路径。如前文所述,要获取与 test.App.class 同一个包下的 App.js 文件,应使用 App.class.getResource('App.js') 。当然,事无绝对,用 ClassLoader.getResource('test/App.js') 也可以,这取决于你所面对的问题是什么。 如果对 ClassLoader 不太了解,那就尽量使用 Class 的 getResource 方法。 如果不理解或无法确定该传给 Class.getResource 方法的相对路径,那就以类路径的顶层包路径为参考起点,总是传给它以 '/' 开头的路径吧。 不要假设你的调试环境就是最后的运行环境。你的代码可能不打包,也可能打包,你得考虑这些情况,不要埋坑。getResources: 枚举资源

Java 的 CLASSPATH 是一个路径列表,因此,有可能在多个类路径中出现同样的资源名。如果要列举它们,可以使用 ClassLoader 的 getResources 方法。

下面的代码可以枚举所有的 'META-INF/MANIFEST.MF',你还可以观察到在类路径中哪些 jar 文件包含有该资源:

import java.net.URL;import java.util.Enumeration;public class Test { public static void main(String[] args) throws Exception {ClassLoader ldr = Test.class.getClassLoader();System.out.println('## Test for getResources(‘META-INF/MANIFEST.MF’) ##');Enumeration<URL> urls = ldr.getResources('META-INF/MANIFEST.MF');while(urls.hasMoreElements()) System.out.println(urls.nextElement()); }}实例

下面的代码演示了如何正确获取代码的类路径起点:

/** * Copyright (c) 2014 Chen Zhiqiang <chenzhiqiang@mail.com>. Released under the MIT license. */package test;import java.io.File;import java.net.MalformedURLException;import java.net.URL;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 演示如何获取当前类路径的起点 * * @author Chen Zhiqiang <chenzhiqiang@mail.com> */public class AppDirTest {Classcls = AppDirTest.class;URL codeLocation = getCodeLocation();/** * Get the code location. * * Return the classpath where the code run from. The return url will be: * file:/path/my-app/calsses/ or file:/path/my-app/my-app.jar * * @return URL */public URL getCodeLocation() {if (codeLocation != null)return codeLocation;// Get code location using the CodeSourcecodeLocation = cls.getProtectionDomain().getCodeSource().getLocation();if (codeLocation != null)return codeLocation;// If CodeSource didn’t work, use {@link } Class.getResource instead.URL r = cls.getResource('');synchronized (r) {String s = r.toString();Pattern jar_re = Pattern.compile('jar:s?(.*)!/.*');Matcher m = jar_re.matcher(s);if (m.find()) { // the code is run from a jar file.s = m.group(1);} else {String p = cls.getPackage().getName().replace(’.’, ’/’);s = s.substring(0, s.lastIndexOf(p));}try {codeLocation = new URL(s);} catch (MalformedURLException e) {throw new RuntimeException(e);}}return codeLocation;}/** * Get the class path root where the program startup, if run in a jar, * return the jar file’s parent path. * * @return */public String getAppDir() {File f = new File(getCodeLocation().getPath());return f.isFile() ? f.getParent() : f.getPath();}public static void main(String[] args) {AppDirTest t = new AppDirTest();System.out.println('code location: ' + t.getCodeLocation());System.out.println('app dir: ' + t.getAppDir());}}

以上就是Java 正确地从类路径中获取资源的详细内容,更多关于Java 从类路径中获取资源的资料请关注好吧啦网其它相关文章!

标签: Java
相关文章: