2013-11-20 78 views
9

例如,如果com.google程序包存在于某个JAR中(例如Guava),则此代码段会在stream.read()行上抛出NullPointerException(!)。有没有办法判断类路径资源是文件还是目录?

ClassLoader classLoader = getClass().getClassLoader(); 
URL resource = classLoader.getResource("com/google"); 
InputStream stream = resource.openStream(); 
System.out.println(stream.toString()); // Fine -- stream is not null 
stream.read(); // NPE inside FilterInputStream.read()! 

如果com/google与一个包,是在文件系统,而不是一个JAR交换,那么段不会崩溃的。事实上,它似乎读取该目录中的文件,并用换行符分隔,尽管我无法想象该行为是在任何地方指定的。

如果资源路径“com/google”指向“普通”资源文件或目录,是否有方法测试?

+0

'try {stream.read(); System.out.println(“它在文件系统上!”); } catch(NullPointerException npe){System.out.println(“它在JAR!”); }' –

+0

@AdrianPetrescu我想区分类路径目录和文件,而不是“JAR文件”和“文件系统上的文件或目录”。另外,我宁愿不依赖任何无证件。 –

回答

8

由于涉及加载这些资源的协议处理程序的某些未指定的行为,这有点混乱。在这种特殊情况下,有两个:sun.net.www.protocol.file.Handlersun.net.www.protocol.jar.Handler,它们每个处理目录情况有点不同。根据一些实验,这里就是他们每做:

sun.net.www.protocol.file.Handler

  • 这是什么Handler确实是开放FileURLConnection,遇到时确实exactly what you discovered it did与一个目录。你可以检查它是否是一个目录只是:

    if (resource.getProtocol().equals("file")) { 
        return new File(resource.getPath()).isDirectory(); 
    } 
    

sun.net.www.protocol.jar.Handler

  • Handler,而另一方面,打开JarURLConnection其最终makes its wayZipCoder。如果你看一下这段代码,你会发现一些有趣的事情:jzentry将从本地JNI调用返回null,因为JAR zip文件实际上并不包含名为com/google的文件,所以它返回null给包裹它的流。

但是,有一个解决方案。虽然ZipCoder不会找到com/google,但它找到com/google/(这是大多数ZIP接口工作,出于某种原因)。在这种情况下,jzentry将被找到,它只会返回一个空字节。

因此,通过切入所有这些随机实现特定的行为,您可能首先尝试访问具有尾随/which is what URLClassLoaders expect for directories anyway)的资源,以确定它是否为目录。如果ClassLoader.getResource()返回非空,那么它是一个目录。如果没有,请尝试不使用斜线。如果它返回非空,它是一个文件。如果它仍然返回null,那么它甚至不是现有的资源。

有点哈克,但我不认为还有什么更好的。我希望这有帮助!

+1

这太好了,谢谢。不幸的是,即使'file.txt'是一个常规文件,getResource(“path/to/file.txt /”)也会返回非null。 –

+0

哇。去Java。好的,这只会发生在'file'协议,还是'jar'协议?我怀疑只有前者。 –

+0

如果这只发生在'file'上,就像我猜想的那样,那么只需使用上面的'resource.getProtocol()'检查来分别处理这种情况,而末尾的'''技巧仍然适用于'jar' 。 –

-3

我认为有2个解决方案。

  1. 朴素的解决方案基于路径本身的分析。如果以.jar.zip.war.ear结尾,则它是文件。否则它是一个目录。我认为这种方法可以在99.99%的情况下工作,除非有人试图让你失败。例如,通过定义看起来像目录但是文件的软链接或反之亦然。
  2. 尝试模仿相对于当前工作目录解释类路径路径的JVM逻辑。因此,通过使用new File(".")来检索当前工作目录,然后取出类路径,对其进行拆分,并且对于它的每个元素使用new File(".", classPathElement),除非它是使用绝对路径定义的。

祝你好运。

+1

也许我可以让我的问题更清楚。我不是在谈论classpath * entries *,我正在谈论classpath *资源*,即这些JAR/WAR中的文件。例如,Maven发言中的src/main/resources。 –

+0

@塔维恩巴恩斯,你的问题很清楚。这就是我正在谈论的。一旦你可以读取classpath条目,你肯定可以确定类路径资源的类型。如果条目是文件路径,则只需使用常规文件API来检查资源的类型。如果它是一个jar,使用'JarInputStream' – AlexR

+0

噢好吧,我没有得到你想要扫描类路径并自己打开条目。虽然这是合理的,但我希望能够使用现有的ClassLoader基础结构处理任意类路径。否则,我的应用将在异国情调中破碎。 –

1

有没有安全通用方法来检测这一点。当您使用ClassLoader.getResource()时,ClassLoader实际上可以返回URL中的任何内容,原则上甚至是您以前从未见过的类加载器实现其自己的URL方案(和协议)的东西。

你唯一的选择是分析getResource()返回的URL,协议应该暗示它是什么(例如“file://”)。但要小心,取决于环境,它可能会返回你不打算的东西。

但是,只是访问资源,你不需要护理它来自哪里(你可能会关心你是否正在调试配置问题,但你的代码不应该关心)。

一般来说,您不应该对返回的InputStream的能力做出假设,即不要依赖它支持标记/重置等。唯一安全的操作就是简单地读取Stream。如果在读取期间发生IOException,则表示访问资源时出现问题(网络连接丢失等)。编辑:getResource()应IMO只返回资源(例如文件或zip文件条目),但从不目录(因为他们不是资源)。然而,我不会指望每个可能的ClassLoader都这样做,我不确定什么是正确的行为(如果它甚至指定在某处)。

+0

我觉得答案可能是这样的。我这样对待类路径资源,但是如果有人不小心将'com/company'而不是'com/company/resource.txt'传递给我的'read()',我想要做一些更聪明的事情API –

+0

如果URL按照您的代码片段的建议向您返回了一些延迟检测错误的流,我只会在第一次阅读时接受它失败。我对此的唯一挑剔是它确实应该抛出IOException而不是NPE。但是我肯定不会在JRE中打破一条腿来弥补怪异。垃圾进垃圾出。 – Durandal

+0

不幸的是,在这种情况下,这是一个垃圾进入,很长一段时间花在远程调试一个奇怪的堆栈跟踪。 –

相关问题