微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Java 15 ModuleFinder 遇到读取模块出错

如何解决Java 15 ModuleFinder 遇到读取模块出错

我正在尝试以编程方式加载一个使用 Java 平台模块系统的项目,以便我可以分析存在多少模块以及它们之间的关系。我写了以下代码

public void retrieveModules() throws IOException {
    moduleRefs = new ArrayList<ModuleReference>();

    // search for all module-info.class
    Stream<Path> classpaths = Files.walk(Paths.get(this.basePath));
    List<String> moduleInfoClassFiles = classpaths
            .filter(p -> p.getFileName()
                    .toString()
                    .toLowerCase()
                    .contains("module-info.class"))
            .map(p -> p.toString())
            .collect(Collectors.toList());

    // load modules
    for (String classFilePath : moduleInfoClassFiles) {
        Path dir = Paths.get(classFilePath.replace("/module-info.class",""));
        ModuleFinder finder = ModuleFinder.of(dir);
        moduleRefs.addAll(finder.findAll());
    }
}

代码运行良好,因为 finder.findAll() 找到了模块,我可以检索正确的模块描述符。但是,当我尝试在 JUnit 5 (https://github.com/junit-team/junit5)

上运行它时

我收到以下错误消息:

Exception in thread "main" java.lang.module.FindException: Error reading module: /.../junit-team_junit5/junit-platform-engine/build/classes/java/module/org.junit.platform.commons
    at java.base/jdk.internal.module.ModulePath.readModule(ModulePath.java:350)
    at java.base/jdk.internal.module.ModulePath.scan(ModulePath.java:237)
    at java.base/jdk.internal.module.ModulePath.scanNextEntry(ModulePath.java:190)
    at java.base/jdk.internal.module.ModulePath.findAll(ModulePath.java:166)
    at RepoAnalyzer.retrieveModules(RepoAnalyzer.java:41)
    at RepoAnalyzer.main(RepoAnalyzer.java:23)
Caused by: java.lang.module.InvalidModuleDescriptorException: Package org.junit.platform.commons.util not found in module

我试图找出问题所在。我验证了包已正确导出并且两个模块都需要。有谁知道我做错了什么?

解决方法

无需对您遇到的路径执行字符串操作。您的代码可以简化为

public void retrieveModules() throws IOException {
    // search for all module-info.class
    try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
        moduleRefs = ModuleFinder.of(classPaths
            .filter(p -> p.getFileName().toString().equals("module-info.class"))
            .map(Path::getParent)
            .toArray(Path[]::new)).findAll();
    }
}

假设您可以将 moduleRefs 更改为 SetCollection 而不是 List。否则,将表达式包装在 new ArrayList<>(…) 中。

您似乎正在扫描默认文件系统上的“爆炸模块”,并且遇到的 module-info.class 没有明确声明所有包。 ModuleFinder 内部发生的事情相当于调用 ModuleDescriptor.read(InputStream in,Supplier<Set<String>> packageFinder) 而提供的包查找器将扫描包含模块信息的目录的子树,以查找至少包含一个常规文件、类或资源的目录,并具有构成有效包名称的名称。所有这些都被视为模块的包。

该方法的文档说明:

投掷

InvalidModuleDescriptorException - 如果检测到无效的模块描述符或 packageFinder 返回的包集不包括从模块描述符获取的所有包

从模块描述符中获取的包在packages() method中命名:

包集包括所有导出和打开的包,以及任何服务提供者的包,以及主类的包。

因此包 org.junit.platform.commons.util 已通过 module-info 标识为必需,但未通过包查找器找到,即相应的目录可能不存在。

您可以尝试通过手动回溯这些步骤来缩小问题范围:

public void checkModules() throws IOException {
    try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
        classPaths
            .filter(p -> p.getFileName().toString().equals("module-info.class"))
            .forEach(this::check);
    }
}
private void check(Path path) {
    try {
        ModuleDescriptor md = md(path);
        System.out.println("module " + md.name());

        for(String pkg: md.packages()) {
            Path pLoc = path.getParent();
            for(String sub: pkg.split("\\.")) pLoc = pLoc.resolve(sub);
            System.out.print("package " + pkg + ": " + pLoc);
            if(!Files.exists(pLoc)) System.out.println(" does not exist");
            else if(!Files.isDirectory(pLoc)) System.out.println(" is not a directory");
            else if(!checkClassOrResource(pLoc))
                System.out.println(" contains no class or resource");
            else System.out.println(" seems ok");
        }
    }
    catch(IOException ex) {
        throw new UncheckedIOException(ex);
    }
}
private boolean checkClassOrResource(Path pLoc) throws IOException {
    try(Stream<Path> members = Files.list(pLoc)) {
        return members.anyMatch(Files::isRegularFile);
    }
}
private ModuleDescriptor md(Path p) throws IOException {
    try(InputStream is = Files.newInputStream(p)) {
        return ModuleDescriptor.read(is);
    }
}

这将检查与包对应的目录并打印诊断消息。请注意,上面使用的没有包查找器的方法 ModuleDescriptor.read(InputStream is) 变体不会验证包位置,而是仅使用在 module-info 中找到的那些,它已经包含与跨模块关系相关的所有包。如果您对分析中的其他包不感兴趣,您可以简单地使用该方法为每个 ModuleDescriptor 构造一个 module-info 并继续进行处理。

try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
    List<ModuleDescriptor> modules = classPaths
        .filter(p -> p.getFileName().toString().equals("module-info.class"))
        .map(path -> {
            try(InputStream is = Files.newInputStream(path)) {
                return ModuleDescriptor.read(is);
            } catch(IOException ex) {
                throw new UncheckedIOException(ex);
            }
        })
        .collect(Collectors.toList());
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。