从外部模块访问资源文件

时间:2017-10-21 08:46:59

标签: java java-9 jigsaw

到非模块化java为止,你只需将文件放在src/main/java/resources中,确保它在classpath中,然后用

加载它
file = getClass().getClassLoader().getResourceAsStream("myfilename"); 

来自类路径中的几乎任何地方。

现在有了模块,情节会变粗。

我的项目设置如下:

module playground.api {
    requires java.base;
    requires java.logging;
    requires framework.core;
}

配置文件放在src/main/resources/config.yml内。

项目以

运行
java -p target/classes:target/dependency -m framework.core/com.framework.Main

由于主类不在我自己的项目中,而是在外部框架模块中,因此无法看到config.yml。现在的问题是,有没有办法以某种方式将我的配置文件放入模块或打开它?我是否必须更改框架上游加载文件的方式?

我尝试在module-info中使用“exports”或“opens”,但它想要一个包名,而不是文件夹名。

如何以最佳实用的方式实现这一点,以便它可以像在Java 8中一样工作,并尽可能少地进行更改?

3 个答案:

答案 0 :(得分:6)

// to scan the module path
ClassLoader.getSystemResources(resourceName)

// if you know a class where the resource is
Class.forName(className).getResourceAsStream(resourceName)

// if you know the module containing the resource
ModuleLayer.boot().findModule(moduleName).getResourceAsStream(resourceName)

请参阅下面的工作示例。

假设:

.
├── FrameworkCore
│   └── src
│       └── FrameworkCore
│           ├── com
│           │   └── framework
│           │       └── Main.java
│           └── module-info.java
└── PlaygroundApi
    └── src
        └── PlaygroundApi
            ├── com
            │  └── playground
            │      └── api
            │          └── App.java
            ├── config.yml
            └── module-info.java

Main.java可能是

package com.framework;

import java.io.*;
import java.net.URL;
import java.util.Optional;
import java.util.stream.Collectors;

public class Main {
    public static void main( String[] args )
    {
        // load from anywhere in the modulepath
        try {
            URL url = ClassLoader.getSystemResources("config.yml").nextElement();
            InputStream is = url.openStream();
            Main.read(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // load from the the module where a given class is
        try {
            InputStream is = Class.forName("com.playground.api.App").getResourceAsStream("/config.yml");
            Main.read(is);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        // load from a specific module
        Optional<Module> specificModule = ModuleLayer.boot().findModule("PlaygroundApi");
        specificModule.ifPresent(module -> {
            try {
                InputStream is = module.getResourceAsStream("config.yml");
                Main.read(is);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }

    private static void read(InputStream is) {
        String s = new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n"));
        System.out.println("config.yml: " + s);
    }
}

你会用

启动
java --module-path ./FrameworkCore/target/classes:./PlaygroundApi/target/classes \
     --add-modules FrameworkCore,PlaygroundApi \
       com.framework.Main

要克隆此示例:git clone https://github.com/j4n0/SO-46861589.git

答案 1 :(得分:6)

当您使用java命令启动应用程序时,如下所示: -

java -p target/classes:target/dependency -m framework.core/com.framework.Main 
  • 您正在使用-p选项--module-path aternate指定模块路径,该选项将查找目标/类目标/依赖项用于你的模块。

  • 除此之外,使用-m替代--module指定要解析的初始模块,其名称为framework.core,并使用main构建模块图要明确执行的类com.framework.Main

现在,问题似乎是模块framework.core没有requires或读取playground.api模块,因为模块图不包含所需模块由实际资源config.yml组成。

作为suggested by @Alan,在启动期间列出模块分辨率输出的一种好方法是使用--show-module-resolution选项。

  

我只是天真地试图打开src / main / resources,不编译c

由于模块中的资源位于root level,因此 not encapsulated 并且不需要打开或导出到任何其他模块。

在您的情况下,您只需要确保模块playground.api最终出现在模块图中,然后应用程序就可以访问该资源。要指定除初始模块之外要解决的根模块,您可以使用--add-modules选项。

因此,为您工作的整体解决方案以及一些调试应该是:

java --module-path target/classes:target/dependency 
     --module framework.core/com.framework.Main
     --add-modules playground.api
     --show-module-resolution

答案 2 :(得分:0)

在命名模块中运行时,ClassLoader#getResource 具有非常令人惊讶的行为。 ClassLoader#getResourceAsStream 也同样麻烦。我在将应用程序升级到命名模块时遇到了这个问题。

如果您的代码在命名模块中,并且您通过 ClassLoader#getResource 访问资源,必须无条件打开该资源的包。否则,您将无法检索资源,即使资源在同一个模块中。


这与 Class#getResource 的行为不同 - 请注意区别。 Class#getResource 很直接,没有这种令人讨厌的惊喜。

关于 getResource 的所有内容也适用于 getResourceAsStream 方法。

因此,我建议始终在 Class 上使用 getResource 方法,而不是在 ClassLoader 上使用。

另见我对What is the difference between Class.getResource and ClassLoader.getResource

的回答