【问题标题】:How to get classpath from classloader?如何从类加载器获取类路径?
【发布时间】:2012-07-23 13:56:47
【问题描述】:

我正在使用一些第三方代码,当给定“-classpath”命令行参数时,它不会设置 java.class.path,而是创建一个类加载器,为命令行指定类路径上的项目添加所有 url到类加载器,然后将其设置为上下文类加载器。 在我编写的这段代码的插件类中,我得到了这个类加载器的一个实例,并且不知何故需要使用它来取回底层的类路径,以便我可以在调用 JavaCompiler.getTask(... ) 并即时编译一些其他代码。 但是,似乎无论如何都无法从 ClassLoader 获取 ClassPath,并且由于 java.class.path 未设置,我似乎无法访问应用程序最初调用的底层类路径......有什么想法吗?

【问题讨论】:

    标签: java classloader contextclassloader


    【解决方案1】:

    如果类加载器使用 URL,它必须是 URLClassloader。您可以访问的是为它定义 classpath 及其父级 ClassLoader 的 URL。

    要获取 URL,只需执行以下操作:

    ((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()
    

    【讨论】:

    • 此解决方案在 Java 9 上不再适用,因为类加载器无法转换为 URLClassLoader
    • G Quintana 是正确的。在此处查看我关于扫描 Java 9+ 模块路径的答案:stackoverflow.com/questions/41932635/…
    • ClassCastException:无法将“jdk.internal.loader.ClassLoaders$AppClassLoader”转换为“java.net.URLClassLoader”
    【解决方案2】:

    今天枚举类路径最简洁的方法是使用ClassGraph 库(我是作者)。请注意,如果您希望您的代码在今天具有可移植性,那么阅读java.class.path 属性或调用((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs() 的旧答案是非常不合适的,因为许多运行时环境不再使用java.class.path,并且/或者它们的类加载器没有扩展URLClassLoader,和/或他们使用一些模糊的机制来扩展类路径(如 jar 清单文件中的 Class-Path: 属性),和/或您的代码可能在 JDK 9+ 中作为模块运行(或者您的代码将可以在 JDK9+ 的传统类路径上运行,但传统类路径的标准 JDK 类加载器甚至不再扩展 URLClassLoader

    ClassGraph 自动处理大量的classpath specification mechanisms and classloader implementations。对于大多数受支持的类加载器,已经为 ClassGraph 编写了自定义反射代码以从类加载器获取类路径(这是必需的,因为ClassLoader API 没有任何获取类路径的标准机制)。您可以为此编写自己的代码,但可能只支持URLClassLoader 而无需花费大量精力——因此最好只使用 ClassGraph,因为工作已经为您完成。

    要获取类路径(以及添加到模块路径的非系统模块化 jar),只需调用:

    List<URI> classpath = new ClassGraph().getClasspathURIs();
    

    请注意,在 Java 9+ 中,模块(或 jlink'd jars)可能会出现在带有 jrt: URI 的列表中,您不能直接使用它(除了使用 ClassGraph 从中读取资源和类之外) ,因为 ClassGraph 可以额外使用 JPMS API 来访问这些资源和类)。您还可以使用 ClassGraph 枚举或扫描类路径中的所有类和/或所有资源(请参阅the ClassGraph wiki)。

    在 Java 9+ 的模块化项目中,您可能还希望获取系统中可见模块的ModuleReference 对象列表。这些可以通过调用以下命令获得(ModuleRef 是向后兼容的ModuleReference 的包装器,因此您可以在 JDK 7/8 上编译代码,但仍可以利用 JDK 9+ 上的模块功能):

    List<ModuleRef> modules =
        new ClassGraph()
            .enableSystemPackages() // Optional, to return system modules
            .getModules();
    

    或者您可以通过调用以下命令获取传递到命令行的实际模块路径(--module-path--patch-module--add-exports 等),返回ModulePathInfo 对象列表:

    List<ModulePathInfo> modulePathInfo = new ClassGraph().getModulePathInfo();
    

    【讨论】:

    • 如果您关注该项目,绝对是最完整的答案并积极发展。
    • 在调用堆栈上查找任何其他类加载器的脏方法正是我所需要的!非常感谢!
    • @luke-hutchison 您的图书馆是救生员!对于任何想要为各种运行时环境获取类路径的解决方案的人来说,这是最好的。
    • 我比较了带有嵌入式 Tomcat 的 Spring-boot 应用程序的 ClassGraphURLClassloader.getURLs() 的输出。 ClassGraph 忽略了/BOOT-INF/classes 文件夹以及&lt;JRE_HOME&gt;/lib/ext 中的一些jar,而getURLs() 忽略了在我的系统临时文件夹中创建的tomcat-docbase.nnn 文件夹。
    • @Pino 这两个目录在 ClassGraph 中都有明确的支持,所以它们应该可以工作。能否请您在 ClassGraph github 站点提交错误报告?
    【解决方案3】:

    以备将来参考,以防您需要将类路径传递给ProcessBuilder

    StringBuffer buffer = new StringBuffer();
    for (URL url :
        ((URLClassLoader) (Thread.currentThread()
            .getContextClassLoader())).getURLs()) {
      buffer.append(new File(url.getPath()));
      buffer.append(System.getProperty("path.separator"));
    }
    String classpath = buffer.toString();
    int toIndex = classpath
        .lastIndexOf(System.getProperty("path.separator"));
    classpath = classpath.substring(0, toIndex);
    ProcessBuilder builder = new ProcessBuilder("java",
        "-classpath", classpath, "com.a.b.c.TestProgram");
    

    【讨论】:

      【解决方案4】:

      如果其他答案不起作用,试试这个:

      ClassLoader cl = ClassLoader.getSystemClassLoader();
      URL[] urls = ((URLClassLoader) cl).getURLs();
      for (URL url: urls) {
          System.out.println(url.getFile());
      }
      

      【讨论】:

        【解决方案5】:

        将此代码拖放到一个空的 jsp 页面中,以查看 classLoader 层次结构和在每个级别加载的相关 jar。

        下面的visit()方法也可以单独使用

        <%!
            public void visit(StringBuilder sb, int indent, ClassLoader classLoader) {
                if (indent > 20 || classLoader == null)
                    return;
                String indentStr = new String(new char[indent]).replace("\0", "    ");
                sb.append("\n");
                sb.append(indentStr);
                sb.append(classLoader.getClass().getName());
                sb.append(":");
                if (classLoader instanceof java.net.URLClassLoader) {
                    java.net.URL[] urls = ((java.net.URLClassLoader)classLoader).getURLs();
                    for (java.net.URL url : urls) {
                        sb.append("\n");
                        sb.append(indentStr);
                        sb.append(url);
                    }
                }
                sb.append("\n");
                visit(sb, indent + 1, classLoader.getParent());
            }
        
        %>
        
        <%
        StringBuilder sb = new StringBuilder();
        visit(sb,1,this.getClass().getClassLoader());
        %>
        <pre>
        <%=sb%>
        </pre>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-11-01
          • 2014-12-06
          • 2021-04-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多