可执行 jar 中 Class.getResource() 和 ClassLoader.getResource() 的奇怪行为

Posted

技术标签:

【中文标题】可执行 jar 中 Class.getResource() 和 ClassLoader.getResource() 的奇怪行为【英文标题】:Strange behavior of Class.getResource() and ClassLoader.getResource() in executable jar 【发布时间】:2012-10-27 11:59:03 【问题描述】:

我从What is the difference between Class.getResource() and ClassLoader.getResource()?了解到 并从自己的代码中,即

getClass().getResource("/path/image.png")

等同于

getClass().getClassLoader().getResource("path/image.png")

发帖Cannot read an image in jar file 显示使用问题

getClass().getClassLoader().getResource("path/image.png")

在可执行的 jar 文件中返回 null,而

getClass().getResource("/path/image.png")

返回正确的 URL。

Since Class.getResource() 在删除前导斜杠后委托给ClassLoader.getResource(),我希望这些调用是相同的,但显然它们不是在这种情况下。即使将特殊的类加载器附加到特定类,每次调用它仍然应该是相同的,再次导致相同的行为。

所以,问题是:是否有任何明显的情况会导致以下情况 代码第一次调用返回 null 但第二次调用返回正确的 URL?

package com.example;

import java.net.URL;

public class ResourceTest 

   public void run() 
      URL iconUrl1 = getClass().getClassLoader().getResource("path/image.png");
      System.out.println("ClassLoader.getResource(\"path/image.png\"): " + iconUrl1);

      URL iconUrl2 = getClass().getResource("/path/image.png");
      System.out.println("Class.getResource(\"/path/image.png\"): " + iconUrl2);
   

   public static void main(String[] args) 
      ResourceTest app = new ResourceTest();
      app.run();
   

【问题讨论】:

您是否重现了这种行为,或者这个问题只是基于您链接的问题?尝试提供 SSCCE,然后人们将能够回答这个问题。 不幸的是,它只是基于我链接的那个 - 我无法重现它。如果可以的话,我肯定会发布一个 SSCCE :),我希望通过这个问题获得一些反馈,以便能够编写重现此类问题的代码。我已经使用调试器进入了 getClass().getResource(),并且看到了上面第一个链接中提到的算法,但我不知道在哪些情况下会发生链接问题中描述的行为。 请添加一个工作示例。我查看了 java.lang 类源和 javadoc 中的信息,但我得到的信息与您已经提到的相同。 @gyabraham 我添加了一个工作样本。知道如何打破它吗? (听起来像是一个奇怪的问题;)) 问:知道如何破解它吗?答:是的。如果你想要一个“null”而不是另一个,你所要做的就是将你的“类”文件放在与“类加载器根”不同的目录中。换句话说,您所要做的就是引入一个包(无论如何您都应该这样做)。我的示例在我的机器上执行此操作:1/2 案例是“SUCCESS”,1/2 是“NULL”。我正在使用 JDK 1.6。我知道当您运行 我的相同代码 时,您会得到 all “SUCCESS” 而没有“NULL”。问:对吗? 【参考方案1】:

我以为这个问题已经被问及回答了!

What is the difference between Class.getResource() and ClassLoader.getResource()?

getClass().getResource() 搜索相对于 .class 文件,而 getClass().getClassLoader().getResource() 搜索相对于 类路径根目录。

如果这里有 SSCCE,我不明白为什么没有

1) 在 .jar 中显示目录组织,然后...

2) 考虑包装

问:What is the difference between Class.getResource() and ClassLoader.getResource()?(以及它引用的链接)尚未回答什么(如果有的话)?

================================================ ============================

我仍然不确定有什么不清楚的地方,但这个例子可能会有所帮助:

/*
  SAMPLE OUTPUT:
  ClassLoader.getResource(/subdir/readme.txt): NULL
  Class.getResource(/subdir/readme.txt): SUCCESS

  ClassLoader.getResource(subdir/readme.txt): SUCCESS
  Class.getResource(subdir/readme.txt): NULL
 */
package com.so.resourcetest;

import java.net.URL;

public class ResourceTest 

    public static void main(String[] args) 
        ResourceTest app = new ResourceTest ();
    

    public ResourceTest () 
        doClassLoaderGetResource ("/subdir/readme.txt");
        doClassGetResource ("/subdir/readme.txt");
        doClassLoaderGetResource ("subdir/readme.txt");
        doClassGetResource ("subdir/readme.txt");
    

    private void doClassLoaderGetResource (String sPath) 
        URL url  = getClass().getClassLoader().getResource(sPath);
        if (url == null)
            System.out.println("ClassLoader.getResource(" + sPath + "): NULL");
        else
            System.out.println("ClassLoader.getResource(" + sPath + "): SUCCESS");
    

    private void doClassGetResource (String sPath) 
        URL url  = getClass().getResource(sPath);
        if (url == null)
            System.out.println("Class.getResource(" + sPath + "): NULL");
        else
            System.out.println("Class.getResource(" + sPath + "): SUCCESS");
    

这是相应的目录树。它恰好是一个 Eclipse 项目,但无论是 Eclipse、Netbeans ... 还是 .jar 文件,目录都是相同的:

C:.
├───.settings
├───bin
│   ├───com
│   │   └───so
│   │       └───resourcetest
│   └───subdir
└───src
    ├───com
    │   └───so
    │       └───resourcetest
    └───subdir

打开的文件是“subdir/readme.txt”


附录 11/9/12:

嗨-

我从 github 逐字复制您的代码,重新编译并重新运行:

ClassLoader.getResource(/subdir/readme.txt): NULL
Class.getResource(/subdir/readme.txt): SUCCESS
ClassLoader.getResource(subdir/readme.txt): SUCCESS
Class.getResource(subdir/readme.txt): NULL

如果那不是你得到的输出......我很困惑。

不惜一切代价,我正在奔跑:

Eclipse Indigo(没关系)

在 IDE 内部运行(不管是文件系统还是 .jar,在 IDE 内部还是外部)

我的 JRE 是 1.6(如果有的话,这可能是大问题)

抱歉,我们无法解决我认为简单的问题:(


附录 11/21/12(安德烈亚斯):

由于最近没有关于这个问题的活动,我想总结一下我们的发现:

根据我们的普遍理解,上述问题的答案是:“不,不可能Class.getResource("/path/image.png")返回有效URL,而ClassLoader.getResource("path/image.png")返回null”: 我们非常清楚 ClassLoader.getResource() 和 Class.getResource() 之间的区别 我们的示例输出匹配,“SUCCESS”和“null” 样本输出符合我们的预期 结论:要么我们监督了某些事情,要么某些不同的事情导致链接问题中描述的“解决方案”起作用。我认为我们目前无法证明其中之一。

【讨论】:

我还没有看到答案的问题是:getClass().getResource("/path/image.png"); 如何在一个可执行的 jar 文件中返回一个 URL 而getClass().getClassLoader().getResource("path/image.png"); 返回 null (假设包装相同)。我的理解是“这是不可能的,至少在普通标准 JDK/JRE 环境中是不可能的”。在这种情况下,原始(链接)问题的 OP 将具有完全不同的根本问题。否则,请告诉我如何打包我的示例代码以实现此行为:) 您的样本完全有效,与我的理解和样本的输出没有区别。不过,问题是:有没有办法将它打包成一个可执行的 jar 文件,这样Class.getResource(/subdir/readme.txt) 返回SUCCESS。但ClassLoader.getResource(subdir/readme.txt) 返回NULL 是的,该死!!!!这正是我的第一个示例所做的!!!!请查看示例输出 - 或自行运行。在您自己的目录或您自己的 .jar 中 - 它们完全等效!!!!请……看……再次!请记住:1)“包”意味着“目录路径”,2)加载器的“绝对路径”是一回事,3)“类”的“绝对路径”是,依赖于包,一个 不同的东西!!!! @Andreas - 问:您看到示例如何为 Class.getResource(/subdir/readme.txt) 返回“SUCCESS”,为 ClassLoader.getResource(subdir/readme.txt) 返回“null” ?你自己试过了吗?使用 .jar 文件? 不,当我运行您的示例时,ClassLoader.getResource(subdir/readme.txt) 不会返回“null”,而是返回成功,请参阅pastebin.com/eMgAiLNy。对于两者,从文件系统或可执行 jar 运行。这也是您的示例代码中的注释所说的。有关完整的可运行示例,请参阅 github.com/afester/***/tree/master/getResource - build shell 脚本显示了 jar 文件是如何构建和执行的。

以上是关于可执行 jar 中 Class.getResource() 和 ClassLoader.getResource() 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Eclipse:如何使用外部 jar 构建可执行 jar?

在可执行 jar 中访问 derby 数据库

springboot创建一个可执行的jar

创建可执行的JAR包并运行

maven工程编译并生成可执行JAR包命令

从main()方法中获取可执行jar的名称[重复]