Java 如何从类加载器获取类路径?

Java 如何从类加载器获取类路径?,java,classloader,contextclassloader,Java,Classloader,Contextclassloader,我使用的是一些第三方代码,当给它一个'-classpath'命令行参数时,它不会设置java.class.path,而是创建一个类加载器,将命令行指定类路径上的项目的所有URL添加到类加载器,然后将其设置为上下文类加载器。 在我编写的这段代码的插件类中,我得到了这个类加载器的一个实例,并且不知何故需要使用它来获取底层类路径,这样我就可以在调用JavaCompiler.getTask(…)时使用它,并动态编译其他一些代码。 然而,似乎无论如何都无法从类加载器获取类路径,而且由于java.class

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

如果类加载器使用URL,它必须是
URLClassloader
。您可以访问的是定义类路径的URL,它与其父类加载程序一起定义类路径

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

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

为便于将来参考,如果您需要将类路径传递到
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");

如果其他答案无效,请尝试以下方法:

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

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

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

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

List<URI> classpath = new ClassGraph().getClasspathURIs();
或者,通过调用以下命令,返回对象列表,可以获得传递到命令行的实际模块路径(
--模块路径
--补丁模块
--添加导出
等):

List modulePathInfo=new ClassGraph().getModulePathInfo();

将此代码放到一个空jsp页面中,以查看类加载器层次结构和在每个级别加载的相关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>
20 | |类加载器==null)
返回;
String indentStr=新字符串(新字符[indent])。替换(“\0”,”);
某人附加(“\n”);
某人追加(契约);
sb.append(classLoader.getClass().getName());
某人加上(“:”);
if(java.net.URLClassLoader的类加载器实例){
java.net.URL[]URL=((java.net.URLClassLoader)classLoader).getURL();
for(java.net.URL:URL){
某人附加(“\n”);
某人追加(契约);
sb.append(url);
}
}
某人附加(“\n”);
访问(sb,indent+1,classLoader.getParent());
}
%>

如果您遵循该项目,这肯定是最完整的答案,并且会不断发展。在调用堆栈上查找任何其他类加载器的肮脏方法正是我所需要的!非常感谢@卢克·哈奇森你的图书馆是救命稻草!对于任何想要为各种运行时环境获取类路径的解决方案的人来说,这是最好的解决方案。我比较了带有嵌入式Tomcat的Spring boot应用程序的
ClassGraph
URLClassloader.getURLs()
的输出。ClassGraph忽略了
/BOOT-INF/classes
文件夹以及
/lib/ext
中的一些jar,而
getURL()
忽略了在我的系统临时文件夹中创建的
tomcat docbase.nnn
文件夹。@Pino这两个目录在ClassGraph中都有明确的支持,所以它们应该可以工作。你能在ClassGraph github站点上提交一个bug报告吗?这个解决方案在Java 9上不再有效,因为类加载器不能被强制转换到URLClassLoaderG Quintana是正确的。请参见我关于扫描Java 9+模块路径的回答:ClassCastException:无法将'jdk.internal.loader.ClassLoaders$AppClassLoader'强制转换为'Java.net.URLClassLoader'
List<ModulePathInfo> modulePathInfo = new ClassGraph().getModulePathInfo();
<%!
    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>