【问题标题】:Preferred way of loading resources in Java在 Java 中加载资源的首选方式
【发布时间】:2021-01-25 21:22:39
【问题描述】:

我想知道在 Java 中加载资源的最佳方式:

  • this.getClass().getResource() (or getResourceAsStream()),
  • Thread.currentThread().getContextClassLoader().getResource(name),
  • System.class.getResource(name)

【问题讨论】:

    标签: java resources


    【解决方案1】:

    嗯,如果您实际上在派生类中,这部分取决于您想要发生的事情。

    例如,假设 SuperClass 在 A.jar 中,SubClass 在 B.jar 中,并且您在 SuperClass 中声明的实例方法中执行代码,但其中 this 指的是SubClass。如果您使用this.getClass().getResource(),它将看起来相对于 B.jar 中的SubClass。我怀疑这通常不是必需的。

    就我个人而言,我可能最常使用Foo.class.getResourceAsStream(name) - 如果您已经知道您所追求的资源的名称,并且您确定它相对于Foo 的位置,那么这是最可靠的方法做海事组织。

    当然,有时这也不是您想要的:根据案情来判断每个案件。只是“我知道这个资源与这个类捆绑在一起”是我遇到的最常见的。

    【讨论】:

    • skeet:如果我们在超类的实例方法中执行语句,那么语句“您正在 SuperClass 的实例方法中执行代码,但是 this 指的是 SubClass 的实例”中存在疑问this" 将引用超类而不是子类。
    • @Suresh:不,不会。试试吧!创建两个类,使一个派生自另一个,然后在超类中打印出this.getClass()。创建一个子类的实例并调用该方法...它会打印出子类的名称,而不是超类的名称。
    • 感谢子类实例方法调用超类方法。
    • 我想知道的是,使用 this.getResourceAsStream 是否只能从与 this 类相同的 jar 加载资源,而不能从另一个 jar 加载。据我估计,加载资源的是类加载器,并且肯定不会被限制从仅从一个 jar 加载?
    【解决方案2】:

    根据你的需要制定解决方案...

    getResource/getResourceAsStream() 将从调用它的类中得到两件事...

    1. 类加载器
    2. 起始位置

    如果你这样做了

    this.getClass().getResource("foo.txt");
    

    它将尝试从与“this”类和“this”类的类加载器相同的包中加载 foo.txt。如果您在前面加上“/”,那么您绝对是在引用该资源。

    this.getClass().getResource("/x/y/z/foo.txt")
    

    将从“this”的类加载器和 x.y.z 包中加载资源(它需要与该包中的类位于同一目录中)。

    Thread.currentThread().getContextClassLoader().getResource(name)
    

    将使用上下文类加载器加载,但不会根据任何包解析名称(必须绝对引用)

    System.class.getResource(name)
    

    将使用系统类加载器加载资源(它也必须被绝对引用,因为您无法将任何内容放入 java.lang 包(System 的包)中。

    只需查看源代码即可。也表示 getResourceAsStream 只是在 getResource 返回的 URL 上调用“openStream”并返回。

    【讨论】:

    • AFAIK 包名无关紧要,重要的是类加载器的类路径。
    • @Bart 如果您查看源代码,您会注意到当您在类上调用 getResource 时类名确实很重要。这个调用做的第一件事是调用“resolveName”,如果合适的话,它会添加包前缀。 resolveName 的 Javadoc 是“如果名称不是绝对的,则添加包名称前缀 如果名称是绝对的,则删除前导的“/””
    • 啊,我明白了。这里的绝对是指相对于类路径,而不是文件系统绝对。
    • 我只是想补充一点,您应该始终检查从 getResourceAsStream() 返回的流不为空,因为如果资源不在类路径中。
    • 另外值得注意的是,使用上下文类加载器允许在运行时通过Thread#setContextClassLoader 更改类加载器。如果您需要在程序执行时修改类路径,这很有用。
    【解决方案3】:

    我搜索三个地方,如下所示。欢迎评论。

    public URL getResource(String resource){
    
        URL url ;
    
        //Try with the Thread Context Loader. 
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if(classLoader != null){
            url = classLoader.getResource(resource);
            if(url != null){
                return url;
            }
        }
    
        //Let's now try with the classloader that loaded this class.
        classLoader = Loader.class.getClassLoader();
        if(classLoader != null){
            url = classLoader.getResource(resource);
            if(url != null){
                return url;
            }
        }
    
        //Last ditch attempt. Get the resource from the classpath.
        return ClassLoader.getSystemResource(resource);
    }
    

    【讨论】:

    • 谢谢,这是个好主意。正是我需要的。
    • 我正在查看您代码中的 cmets,最后一个听起来很有趣。不是所有资源都从类路径加载吗? ClassLoader.getSystemResource() 会涵盖哪些情况下上述没有成功?
    • 老实说,我不明白您为什么要从 3 个不同的地方加载文件。你不知道你的文件存储在哪里吗?
    • “Loader”不应该是 Classloader 的一个实例吗?那具体是什么类型?
    【解决方案4】:

    我知道另一个答案真的很晚,但我只是想分享最后帮助我的东西。它还将从文件系统的绝对路径(不仅是类路径)加载资源/文件。

    public class ResourceLoader {
    
        public static URL getResource(String resource) {
            final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
            classLoaders.add(Thread.currentThread().getContextClassLoader());
            classLoaders.add(ResourceLoader.class.getClassLoader());
    
            for (ClassLoader classLoader : classLoaders) {
                final URL url = getResourceWith(classLoader, resource);
                if (url != null) {
                    return url;
                }
            }
    
            final URL systemResource = ClassLoader.getSystemResource(resource);
            if (systemResource != null) {
                return systemResource;
            } else {
                try {
                    return new File(resource).toURI().toURL();
                } catch (MalformedURLException e) {
                    return null;
                }
            }
        }
    
        private static URL getResourceWith(ClassLoader classLoader, String resource) {
            if (classLoader != null) {
                return classLoader.getResource(resource);
            }
            return null;
        }
    
    }
    

    【讨论】:

      【解决方案5】:

      我尝试了很多上面建议的方法和功能,但它们在我的项目中不起作用。无论如何,我找到了解决方案,这里是:

      try {
          InputStream path = this.getClass().getClassLoader().getResourceAsStream("img/left-hand.png");
          img = ImageIO.read(path);
      } catch (IOException e) {
          e.printStackTrace();
      }
      

      【讨论】:

      • 在这种情况下你最好使用this.getClass().getResourceAsStream()。如果您查看getResourceAsStream 方法的源代码,您会注意到它的作用与您相同,但方式更智能(如果在类中找不到ClassLoader,则回退)。它还表明您可以在代码中的getClassLoader 上遇到潜在的null ...
      • @PromCompot,正如我所说,this.getClass().getResourceAsStream() 不适合我,所以我使用它。我认为有些人会面临像我这样的问题。
      猜你喜欢
      • 2012-08-25
      • 1970-01-01
      • 1970-01-01
      • 2011-03-18
      • 1970-01-01
      • 1970-01-01
      • 2017-12-23
      相关资源
      最近更新 更多