简体   繁体   English

Java空路径约定,尤指在ClassLoader.getResources中使用的约定

[英]Java empty path convention, especially that used in ClassLoader.getResources

Today, I was surprised (likely due to inexperience) to find out that you can, and it is actually useful to, pass an empty path (literally an empty string) to ClassLoader.getResources , ie ClassLoader.getSystemClassLoader().getResources("") . 今天,我很惊讶(可能是由于缺乏经验)发现你可以,并且它实际上很有用,将一个空路径(字面上是一个空字符串)传递给ClassLoader.getResources ,即ClassLoader.getSystemClassLoader().getResources("") This, from some testing, returns one or two directories of where my application .class files live (and does not include the directories of 3rd-party packages). 从某些测试中,这将返回我的应用程序.class文件所在的一个或两个目录(并且不包括第三方软件包的目录)。 (Example usage: Get all of the Classes in the Classpath .) (示例用法: 获取Classpath中的所有类 。)

Presumably, this is because the Java System ClassLoader is one of the three ClassLoaders that loads my own application classes (cf http://www.oracle.com/technetwork/articles/javase/classloaders-140370.html ), so it's not surprising that URL returned points to the directory of my application class files. 据推测,这是因为Java System ClassLoader是加载我自己的应用程序类的三个ClassLoader之一(参见http://www.oracle.com/technetwork/articles/javase/classloaders-140370.html ),所以这并不奇怪该URL返回指向我的应用程序类文件的目录。

But why, and how, does the empty string achieve this? 但是为什么,以及如何,空字符串实现这一目标? I did not find it documented. 我没有发现它有记录。 Is this empty path derivative of more common Java convention? 这是更常见的Java约定的空路径衍生物吗? It's certainly not Linux - you can't cd into an empty path in bash. 这当然不是Linux的-你不能cd到在bash空路径。 I'd appreciate it if someone can help me understand this. 如果有人能帮助我理解这一点,我会很感激。

In another note, I noticed that getResources(".") achieves the same thing. 在另一个注释中,我注意到getResources(".")实现了相同的功能。

Additions for comment discussion 评论讨论的补充

public class myTest {

    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        URL[] urls = ((URLClassLoader) classLoader).getURLs();
        for (int n = 0; n < urls.length; n++)
            System.out.println(urls[n]);  //lists external.jar

        Enumeration<URL> roots = classLoader.getResources(".");
        while (roots.hasMoreElements()) {
            URL url = roots.nextElement();
            System.out.println("getResources: " + url); //does not list external.jar
        }
    }
}

Command to execute: java -cp ".:external.jar" myTest 要执行的命令: java -cp ".:external.jar" myTest

Why does a getResources(String) invocation match all classpath entries that are directories, when given a resource name of "" or "."? 当给定资源名称“”或“。”时,为什么getResources(String)调用匹配作为目录的所有类路径条目?

I could only speculate. 我只能推测。 For what it's worth, I consider this to be an implementation detail of the particular ClassLoader . 对于它的价值,我认为这是特定ClassLoader的实现细节。 The way the "" and "." 方式“”和“。” resource names are treated is nevertheless somewhat intuitive, from a filesystem user's perspective. 从文件系统用户的角度来看,资源名称的处理方式有点直观。

...and how? ...如何?

The default OpenJDK application ClassLoader (also known as the System ClassLoader ), sun.misc.Launcher$AppClassLoader , is a URLClassLoader descendant with a URL search path comprising the values of the "java.class.path" system property. 默认的OpenJDK应用程序ClassLoader (也称为System ClassLoadersun.misc.Launcher$AppClassLoader是一个URLClassLoader后代,其URL搜索路径包含“java.class.path”系统属性的值。 Its getResources ( getResource as well) method ultimately delegates to sun.misc.URLClassPath$FileLoader.getResource(String, boolean) , which does the following: 它的getResourcesgetResource )方法最终委托给sun.misc.URLClassPath$FileLoader.getResource(String, boolean) ,它执行以下操作:

url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
...
file = new File(dir, name.replace('/', File.separatorChar)); // dir is the equivalent of getBaseURL()'s path component
...
if (file.exists()) {
    return new sun.misc.Resource() {
        ...
        public URL getURL() { return url; } // eventually returned by the ClassLoader
    }
}

Leaving all the URL parsing aside, the resource name is essentially treated as a relative filesystem path, and is "absolutized" against the loader's search path's entries. 抛开所有URL解析,资源名称基本上被视为相对文件系统路径,并且对于加载器的搜索路径条目是“绝对化的”。 Therefore a name argument of "" or "." 因此, name参数为“”或“。”。 matches the search path entries themselves. 匹配搜索路径条目本身。 In other words, all top-level classpath entries are matched and returned, as if they all resided beneath the same root directory. 换句话说,所有顶级类路径条目都匹配并返回,就好像它们都位于同一根目录下一样。 Note that this does not apply to JAR classpath entries, which are instead handled by sun.misc.URLClassPath$JarLoader . 请注意,这不适用于JAR类路径条目,而是由sun.misc.URLClassPath$JarLoader处理。

Why don't these getResources invocations match JAR classpath entries as well? 为什么这些getResources调用也不匹配JAR类路径条目? And why are such classpath entries included in the array returned by URLClassLoader.getURLs() ? 为什么这些类路径条目包含在URLClassLoader.getURLs()返回的数组中?

API-wise... API明智...
These are two unrelated methods, each serving a distinct purpose. 这是两种不相关的方法,每种方法都有不同的用途。 Sometimes they "just happen" to produce the same or similar output—nowhere however do their specifications imply any form of mutual consistency in behaviour. 有时他们“只是发生”以产生相同或相似的输出 - 然而他们的规格却暗示任何形式的行为相互一致。

getResources , according to URLClassLoader 's concrete definition of the term "resource", is specified to return files, directories, or JAR entries beneath its search path. 根据URLClassLoader对术语“资源”的具体定义, getResources被指定为其搜索路径返回文件,目录或JAR条目。 The fact that it also happens to return the search path entries themselves, when they represent directories, is not addressed by its specification, and should hence be treated as an implementation detail (and perhaps a minor specification violation as well) and not be relied upon. 当它们表示目录时,它也恰好返回搜索路径条目本身的事实,它的规范没有解决它,因此应该被视为实现细节(也可能是次要的规范违规),而不是依赖它们。 Likewise, the fact that it does not return JAR search path entries, while inconsistent with the former, does not counter its specification. 同样,它不返回JAR搜索路径条目,而与前者不一致,这一事实并不反对其规范。

getURLs , on the other hand, returns the exact 1 search path entries provided at instantiation time. 另一方面, getURLs返回在实例化时提供的确切的1个搜索路径条目。

Implementation-wise... 实现明智...
Unlike sun.misc.URLClassPath$FileLoader , which, as seen earlier, resolves the resource name against each search path entry's filesystem path, sun.misc.URLClassPath$JarLoader attempts a direct match via JarFile.getEntry(name) , which, for the "", and most likely ".", entry name, obviously fails. sun.misc.URLClassPath$FileLoader不同,如前所述,它根据每个搜索路径条目的文件系统路径解析资源名称, sun.misc.URLClassPath$JarLoader尝试通过JarFile.getEntry(name)进行直接匹配,对于“”,很可能是“。”,条目名称,显然都失败了。 But even if both URLClassPath.Loader s were to interpret the resource name in the same manner, things would not work out as intended, because the embedded JAR filesystem does not support the notion of a root directory. 但即使两个URLClassPath.Loader都以相同的方式解释资源名称,事情也无法按预期工作,因为嵌入式JAR文件系统不支持根目录的概念。

So how am I supposed to retrieve all classpath entries? 那么我应该如何检索所有类路径条目?

To do so independently of the system ClassLoader in effect, use something along the lines of 要独立于系统ClassLoader生效,请使用类似的东西

String[] classPathEntries = System.getProperty("java.class.path").split(File.pathSeparator);

, preferably early on in your main method, before any third-party code is afforded the opportunity to modify the property. ,最好在您的main方法的早期,在任何第三方代码提供修改属性的机会之前。

ClassLoader.getSystemClassLoader() has a return type of java.lang.ClassLoader . ClassLoader.getSystemClassLoader()的返回类型为java.lang.ClassLoader How do we know (for sure) that the returned instance is a sun.misc.Launcher$AppClassLoader ? 我们怎么知道(肯定)返回的实例是sun.misc.Launcher$AppClassLoader

We really don't. 我们真的没有。 The system class loader is implementation-dependent and replaceable. 系统类加载器是依赖于实现和可替换的。 All we can do, as always, is test, eg, 我们所能做的一如既往地是测试,例如,

try {
    ClassLoader sysCl = ClassLoader.getSystemClassLoader();
    // not using single-arg Class.forName, since it would use the ClassLoader of this class,
    // which, in the worst-case scenario of being a non-delegating loader, could attempt to load AppClassLoader itself
    if (Class.forName("sun.misc.Launcher$AppClassLoader", false, sysCl).isAssignableFrom(sysCl.getClass())) {
        // default implementation, _most likely_ a URLClassLoader subclass
    }
    else {
        // System ClassLoader overridden, or not on OpenJDK
    }
}
catch (ReflectiveOperationException roe) {
    // most likely not on OpenJDK
}

, and act accordingly. 并采取相应行动。


1 This might not always hold true, eg, when search path entries "overlap" (one is another's parent), or security constraints apply; 1 这可能并不总是成立,例如,当搜索路径条目“重叠”(一个是另一个父母)或安全约束适用时; refer to the source for sun.misc.URLClassPath for the specifics. 有关详细信息,请参阅sun.misc.URLClassPath的源代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM