我正在使用一些第三方代码,当给定“-classpath”命令行参数时,它不会设置 java.class.path,而是创建一个类加载器,将命令行指定类路径上的项目的所有 url 添加到类加载器,然后将其设置为上下文类加载器。在我编写的这段代码的插件类中,我得到了这个类加载器的一个实例,并且不知何故需要使用它来取回底层的类路径,以便我可以在调用 JavaCompiler.getTask(... ) 并即时编译一些其他代码。但是,似乎无论如何都无法从 ClassLoader 获取 ClassPath,并且由于 java.class.path 未设置,我似乎无法访问应用程序最初调用的底层类路径......有什么想法吗?
5 回答
如果类加载器使用 URL,它必须是URLClassloader
. 您可以访问的是为它定义类路径及其父级的 URL ClassLoader
。
要获取 URL,只需执行以下操作:
((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()
今天枚举类路径最简洁的方法是使用ClassGraph库(我是作者)。请注意,如果您希望您的代码在今天具有可移植性,那么读取java.class.path
属性或调用的旧答案((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()
是非常不合适的,因为许多运行时环境不再使用java.class.path
,和/或它们的类加载器不扩展URLClassLoader
,和/或它们使用一些晦涩的机制用于扩展类路径(如Class-Path:
jar 清单文件中的属性),和/或您的代码可以作为 JDK 9+ 中的模块运行(或者您的代码将在 JDK9+ 中的传统类路径上运行,但标准 JDK 类加载器用于传统的类路径甚至URLClassLoader
不再扩展)。
ClassGraph 自动处理大量的类路径规范机制和类加载器实现。对于大多数受支持的类加载器,已经为 ClassGraph 编写了自定义反射代码,以从类加载器获取类路径(这是必需的,因为ClassLoader
API 没有任何获取类路径的标准机制)。您可以为此编写自己的代码,但它可能只支持URLClassLoader
而无需花费大量精力——因此最好只使用 ClassGraph,因为工作已经为您完成。
要获取类路径(以及添加到模块路径的非系统模块化 jar),只需调用:
List<URI> classpath = new ClassGraph().getClasspathURIs();
请注意,在 Java 9+ 中,模块(或 jlink'd jars)可能会出现在带有jrt:
URI 的列表中,您无法直接使用它(除了使用 ClassGraph 从中读取资源和类之外,因为 ClassGraph 还可以使用JPMS API 来访问这些资源和类)。您还可以使用 ClassGraph 枚举或扫描类路径中的所有类和/或所有资源(请参阅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();
以供将来参考,以防您需要将类路径传递给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());
}
将此代码放到一个空的 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>