打开Jar文件中的服务声明文件。

huangapple go评论67阅读模式
英文:

Opening service declaration file inside a Jar

问题

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文件的文件,但在读取JAR文件内部的文件时遇到了问题。URL资源将会正确加载,importedResources确实包含文件的URI,但在尝试使用readLine读取时会返回null。

项目是Maven项目,
URLs看起来类似于这样:

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

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

英文:
List&lt;URL&gt; resources = new ArrayList&lt;&gt;();
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration&lt;URL&gt; importedResources = classLoader.getResources(&quot;META-INF/services&quot;);
while (importedResources.hasMoreElements()) {
resources.add(importedResources.nextElement());
}
Map&lt;String, String&gt; 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(&quot;/&quot;)) {
URL fileUrl = new URL(resource + &quot;\\&quot; + 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);
}

I have been trying to read the service declaration files in my project (then saving them as a Map with Interface name as key and provider names as value), including the ones from imported Jars, but got stuck at reading files from inside JARS.
Rhe URL resources will load correctly and importedResources does contain the URI to the files but when trying to readLine it will return null.

Project is Maven,
URLs look something like this:

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

Any suggestions? or is there a different approach instead?

答案1

得分: 1

> classLoader.getResources("META-INF/services");
这不起作用。类加载器不处理目录,也不执行“list”命令。这就是为什么首先存在“META-INF/services”!与“给我类路径上所有类文件的列表”这样的抽象由类加载器提供不同,你确实有操作“给我资源X的所有变体,即如果该资源存在多次,我希望获取所有这些变体”,这由SPI(META-INF/services)使用:已编译的代码包含列出类名的众所周知的文件名。这解决了“无法运行列表命令”的困境:无需列出任何内容,只需获取你感兴趣的接口的服务文件,然后加载该文件中列出的每个类。

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

你可以对其进行修改 - 确定哪些jar和磁盘上的目录属于类路径并扫描它们。这需要大量代码,你需要请求一个众所周知的资源(不是目录,而是实际文件),对获取的URL执行toString(),然后将其拆分以知道要做什么。这并不完全符合规则 - 类加载器根本不必基于jar/磁盘,它可以动态生成资源,从网络查询资源或从数据库加载资源。鉴于它是可插拔架构且插件不(也不能)实现“list”命令,这样的破解方法是不完整的。

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

无论是什么让你想:“我知道了!我会列出META-INF/services目录中的所有文件!” - 这都是对你尝试该答案的任何问题的错误答案。

请注意,.getResource("META-INF/services")有时会起作用。但规范不保证它,实际上,正如你发现的那样,在大多数平台/类路径源组合上它不起作用。这个“bug”的问题在于它有时起作用,实际上它不应该(规范没有定义它应该起作用。它也没有严格要求它永远不会起作用)。

英文:

> classLoader.getResources(&quot;META-INF/services&quot;);

This does not work. classloaders don't "do" directories, nor do they "do" a "list" command. This is why META-INF/services exists in the first place! Instead of 'give me a list of e.g. all class files on the classpath' which is simply not an abstraction offered by classloaders, you do have the operation "give me all variants of resource X, i.e. if that resource exists more than once I want them all", and that is used by SPI (META-INF/services): Compiled code contains a well-known file name that lists class names. This solves the 'cannot run list commands' dilemma: There is no need to list anything, just get the services file for the interface you are interested in, and then load each class listed in that file.

This situation is ordinarily fixed by using SPI. You want some sort of bizarro meta-SPI where you want a list of all SPI services for which some file exists. This simply isn't a thing. You can't do what you want.

You COULD hack it - determine which jars and dirs-on-disk are part of the classpath and scan those. This requires a ton of code, you need to ask for a well known resource (not a dir, an actual file), toString() the URL you get, and then tear that to pieces to know what to do. This doesn't fully fit the rules - a classloader doesn't have to be jar/disk based at all, it could generate resources on the fly, query them from a network, or load them from a database. Given that it's a pluggable architecture and plugins don't (and cannot) implement a "list" command, such hacks are incomplete.

And thus, not a good idea.

Whatever made you think: "I know! I'll just list all files in the META-INF/services directory!" - that was the wrong answer to whatever question you had that led you to try that answer.

Note that .getResource(&quot;META-INF/services&quot;) does sometimes work. But the specs do not guarantee it, and, indeed, as you found, on most platform/classpath-source combinations it doesn't. The 'bug' is that it sometimes works at all, it's not really supposed to (the spec doesn't define that it should work. It doesn't exactly demand that it never does either).

huangapple
  • 本文由 发表于 2023年6月19日 10:29:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76503288.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定