我有一个非常简单的程序,它试图读取给定包下的类文件。这在本地运行良好(JDK 11)但是当我在 Red Hat Linux 8 服务器(JDK 11)上运行它时,相同的代码返回一个空数组
[]
.
代码如下:
public static Set<Class> getClassesFromPackage(String packageName) throws ClassNotFoundException {
logger.debug(String.format("Getting classes from package %s", packageName));
InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(packageName.replaceAll("[.]", "/"));
logger.debug("stream: " + stream);
if (stream != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
return reader.lines()
.filter(line -> line.endsWith(".class"))
.map(line -> getClass(line, packageName))
.filter(clazz -> clazz != null)
.collect(Collectors.toSet());
} else {
throw new ClassNotFoundException(String.format("package name %s doesn't exist", packageName));
}
}
我是这样称呼它的:
Set<Class> classes = getClassesFromPackage("foo");
logger.info("classes: " + classes);
本地我得到以下信息:
classes: [class foo.Test]
在服务器上,我得到以下信息:
classes: []
没有抛出或记录错误。
有什么想法吗?
ClassLoader.getSystemClassLoader().getResourceAsStream(packageName.replaceAll("[.]", "/"));
这行不通。如果它适用于 Windows,那是..巧合。规范明确表示这是行不通的。
你想要的,是不可能的。从根本上说,类加载器不支持“请给我包 X 中的每个类”的想法。他们就是不这样做。他们拥有的唯一原语是“给我资源 X”。这就是你得到的。给定一个类名,例如
java.lang.String
,您可以从中派生出它的完全限定二进制名称并适当地斜杠它 (java/lang/String.class
),然后请求该资源,这是 ClassLoader API 支持的。
但是,你不能拿着包裹去索要listing。很简单,因为 ClassLoader 没有
listResources
方法。您正在尝试“读取”资源 java/lang
并期望这会返回文件列表。 API 规范并不表示 ClassLoader 必须以这种方式工作,事实上,正如您现在发现的那样,大多数都不需要。
SPI 是允许“列出”事物的解决方案,但 SPI 是一个选择加入系统,无论您是使用
java.util.ServiceLoader
和 META-INF/services/com.foo.someInterfaceName
风格,还是在 module-info.java
中使用较新的 SPI 东西 - “提供者”有选择被列出。
这让我们回到:不。你不能做你想做的事。
好吧,我想要一匹小马。还有世界和平。你唯一真正的选择是破解它。这意味着:每个新版本的 java 都可能破坏您的代码,并且有可能(甚至可能)JVM 版本、JVM 提供程序(azul、openjdk、adoptium 等)和操作系统的某种组合破坏了您的东西,并且没有人谁会接受你的错误报告,因为它不是错误。
如果你愿意接受那个巨大的头痛,你可以破解它。具体来说,你可以请求一个众所周知的资源,比如.getResource("java/lang/String.class")
,
toString()
你得到的 URL 对象,然后去镇上:弄清楚那个 URL 的含义并为每种资源 URL 编写代码您期望知道如何解压缩该 URL 并完成工作。ClassLoaders 返回的 URL 几乎可以是任何内容,包括
data:
URL 或自定义的
blob://whatever
样式 URL。因此,这实际上是不可能的:您无法编写能够响应任何人都可以编写自定义类加载器的世界的静态代码。然而,绝大多数类加载器都有
file
、
jar
或
jmod
URL。所以,如果你为所有 3 个都写一个处理程序,你可以处理大多数,但不是所有的分发策略。
file:
很简单 - 转换为路径,去掉
String.class
、
lang
和
java
部分,然后添加您感兴趣的包,现在您有一个
Path
对象你可以在
Files.newDirectoryStream
折腾,瞧 - 你有你的班级列表。这not 包中所有类的列表,这是不可能的。这只是包中所有类的列表来自那个特定的来源 - 可能会有更多(类路径上可以有 2 个条目,它们都具有相同的包。这很常见,甚至,一个地方承载实际代码和另一个地方在同一个包中托管单元测试)。 对于
jar:
URL,找到
!
,将
jar:
和
!
之间的东西变成一个路径,将其作为
new JarFile
打开,然后寻找具有正确包前缀的条目,你又得到了你想要什么。对于
jmod:
URL,这有点棘手 - 但
jmod
工具存在并且可以列出 jmod 内容,你可能必须仔细阅读this SO question about how to read jmod files. 是的,这是一个很大的努力。是的,这就是重点 - 您正在解决一个基本问题,即类加载器 API
不允许您列出包内容,但您仍然坚持这样做。这通常会导致“大量脆弱、难以维护的代码”。
另一个选择是找一个图书馆来为你做这些骇人听闻的脆弱的事情。也许reflections 库可以做到这一点。
注意:很明显你从this baeldung tutorial 得到了这条线 - 通常 baeldung 是高质量的。这个条目..不是。