在 Jar 中打开服务声明文件

Opening service declaration file inside a Jar

提问人:Teddy Tsai 提问时间:6/19/2023 更新时间:6/19/2023 访问量:34

问:

List<URL> resources = new ArrayList<>();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

Enumeration<URL> importedResources = classLoader.getResources("META-INF/services");
while (importedResources.hasMoreElements()) {
    resources.add(importedResources.nextElement());
}

Map<String, String> fileContents = Maps.newHashMap();

for (URL resource : resources) {
    InputStream stream = classLoader.getResourceAsStream(resource.getFile());
    Properties props = new Properties(); // for debug
    props.load(stream); // for debug
    BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream()));
    String line;
    while ((line = reader.readLine()) != null) { // Line is always null except for the declarations in my own project
        String fileName = line;
        if (!fileName.endsWith("/")) {
            URL fileUrl = new URL(resource + "\\" + fileName);
            BufferedReader fileReader = new BufferedReader(new InputStreamReader(fileUrl.openStream()));
            StringBuilder content = new StringBuilder();
            String fileLine;
            while ((fileLine = fileReader.readLine()) != null) {
                content.append(fileLine);
            }
            fileReader.close();
            fileContents.put(fileName, content.toString());
        }
    }
}
System.out.println(fileContents);

} catch (IOException e) {
throw new RuntimeException(e);
}

我一直在尝试读取我的项目中的服务声明文件(然后将它们保存为映射,将接口名称保存为键,将提供程序名称保存为值),包括导入的 Jar 中的文件,但在从 JARS 内部读取文件时遇到了困难。 Rhe URL 资源将正确加载,并且 importedResources 确实包含文件的 URI,但在尝试读取 Line 时,它将返回 null。

项目是 Maven, URL 如下所示:

jar:file:/C:/Users/xxx/.m2/repository/org/glassfish/hk2/hk2-locator/2.5.0-b42/hk2-locator-2.5.0-b42.jar!/META-INF/services

有什么建议吗?还是有其他方法?

Java JAR java-IO 服务加载器

评论


答:

1赞 rzwitserloot 6/19/2023 #1

classLoader.getResources("META-INF/services");

这是行不通的。类加载器不“执行”目录,也不“执行”“列表”命令。这就是首先存在的原因!而不是“给我一个列表,例如类路径上的所有类文件”,这根本不是类加载器提供的抽象,你确实有操作“给我资源X的所有变体,即如果该资源存在不止一次,我想要它们”,并且由SPI()使用: 编译的代码包含一个列出类名的已知文件名。这解决了“无法运行列表命令”的困境:无需列出任何内容,只需获取您感兴趣的接口的 services 文件,然后加载该文件中列出的每个类。META-INF/servicesMETA-INF/services

这种情况通常通过使用 SPI 来解决。你想要某种奇怪的元SPI,你想要一个存在某些文件的所有SPI服务的列表。这根本不是一回事。你不能做你想做的事。

您可以破解它 - 确定哪些 jar 和 dirs-on-disk 是类路径的一部分并扫描它们。这需要大量的代码,你需要要求一个众所周知的资源(不是目录,一个实际的文件),你得到的URL,然后把它撕成碎片,知道该怎么做。这并不完全符合规则 - 类加载器根本不需要基于 jar/磁盘,它可以动态生成资源,从网络查询它们,或从数据库加载它们。鉴于它是一个可插拔的架构,并且插件没有(也不能)实现“列表”命令,这样的黑客是不完整的。toString()

因此,这不是一个好主意。

不管是什么让你想:“我知道!我只列出目录中的所有文件!“——对于导致您尝试该答案的任何问题,这都是错误的答案。META-INF/services

请注意,这有时确实有效。但是规范并不能保证这一点,事实上,正如你所发现的,在大多数平台/类路径-源组合中,它并不能保证这一点。“错误”是它有时根本可以工作,但实际上不应该这样做(规范没有定义它应该工作。它并不完全要求它从不这样做)。.getResource("META-INF/services")

评论

0赞 Teddy Tsai 6/19/2023
感谢您的详细回答,所以我想它是否有效可能取决于应用程序运行的 JVM。我知道正确的方法是只使用 ServiceLoader,但出于上下文和好奇心,我遇到了 ServiceLoader 无法在 Apache-Flink 任务管理器节点上加载任何内容的问题,我试图找出原因,同时探索一个快速的“黑客”,正如你所说的那样反思。
1赞 rzwitserloot 6/20/2023
System.out.println(SomeClass.class.getResource("SomeClass.class")),对于任何类,都会打印一个字符串,该字符串通常会告诉您从哪个位置加载类的确切位置。您可以轻松地检查该事物的目录。META-INF/services