从外部模块访问资源文件
Accessing resource files from external modules
到目前为止,在非模块化 java 之前,您只需将文件放入 src/main/java/resources
中,确保它位于 class 路径中,然后使用
加载它
file = getClass().getClassLoader().getResourceAsStream("myfilename");
来自 class 路径中的几乎任何地方。
现在有了模块,剧情变厚了。
我的项目设置如下:
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
由于mainclass不在我自己的项目中,而是在一个外部框架模块中,所以看不到config.yml
。现在的问题是,有没有办法以某种方式将我的配置文件放入模块或打开它?我是否必须更改上游框架加载文件的方式?
我尝试在模块信息中使用 "exports" 或 "opens",但它想要一个包名称,而不是文件夹名称。
如何以最实用的方式实现这一目标,使其像 Java 8 中那样工作,并尽可能少地进行更改?
// 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
当您使用 java
命令启动应用程序时,如下所示:-
java -p target/classes:target/dependency -m framework.core/com.framework.Main
您正在使用选项 -p
替代 --module-path
指定模块路径,这将查找 target/classes和 target/dependency 用于您的模块。
此外,使用 -m
替代 --module
指定 初始模块以解析 名称 framework.core
和构造模块图,其中要执行的主要 class 显式列为 com.framework.Main
.
现在,这里的问题似乎是模块 framework.core
没有 requires
或读取 playground.api
模块,因此模块图不包含所需的模块由实际资源 config.yml
.
组成
与 一样,在启动期间列出模块分辨率输出的一个好方法是使用 --show-module-resolution
选项。
I just naively tried to opens src/main/resources, doesn't compile ofc
由于您模块中的资源位于, it is, therefore, 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
当在命名模块中 运行 时,ClassLoader#getResource
有非常令人惊讶的行为。 ClassLoader#getResourceAsStream
同样麻烦。在将应用程序升级到命名模块时,我自己 运行 参与其中。
如果您的代码在命名模块中,并且您通过ClassLoader#getResource
访问资源,必须无条件打开该资源的包。否则,您将无法检索资源,即使资源在同一个模块中。
这与 Class#getResource
的行为不同 - 请注意区别。 Class#getResource
直截了当,没有这种令人讨厌的惊喜。
关于 getResource
的所有内容也适用于 getResourceAsStream
方法。
出于这个原因,我建议始终在 Class
上使用 getResource 方法,而不是在 ClassLoader
上使用 getResource 方法。
另请参阅我对 What is the difference between Class.getResource and ClassLoader.getResource
的回答
到目前为止,在非模块化 java 之前,您只需将文件放入 src/main/java/resources
中,确保它位于 class 路径中,然后使用
file = getClass().getClassLoader().getResourceAsStream("myfilename");
来自 class 路径中的几乎任何地方。
现在有了模块,剧情变厚了。
我的项目设置如下:
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
由于mainclass不在我自己的项目中,而是在一个外部框架模块中,所以看不到config.yml
。现在的问题是,有没有办法以某种方式将我的配置文件放入模块或打开它?我是否必须更改上游框架加载文件的方式?
我尝试在模块信息中使用 "exports" 或 "opens",但它想要一个包名称,而不是文件夹名称。
如何以最实用的方式实现这一目标,使其像 Java 8 中那样工作,并尽可能少地进行更改?
// 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
当您使用 java
命令启动应用程序时,如下所示:-
java -p target/classes:target/dependency -m framework.core/com.framework.Main
您正在使用选项
-p
替代--module-path
指定模块路径,这将查找 target/classes和 target/dependency 用于您的模块。此外,使用
-m
替代--module
指定 初始模块以解析 名称framework.core
和构造模块图,其中要执行的主要 class 显式列为com.framework.Main
.
现在,这里的问题似乎是模块 framework.core
没有 requires
或读取 playground.api
模块,因此模块图不包含所需的模块由实际资源 config.yml
.
与 --show-module-resolution
选项。
I just naively tried to opens src/main/resources, doesn't compile ofc
由于您模块中的资源位于
在您的情况下,您只需要确保模块 playground.api
最终出现在模块图中,然后应用程序就可以访问该资源。要指定除初始模块之外要解析的根模块,您可以使用 --add-modules
选项。
因此,为您工作的整体解决方案以及一些调试应为:
java --module-path target/classes:target/dependency
--module framework.core/com.framework.Main
--add-modules playground.api
--show-module-resolution
当在命名模块中 运行 时,ClassLoader#getResource
有非常令人惊讶的行为。 ClassLoader#getResourceAsStream
同样麻烦。在将应用程序升级到命名模块时,我自己 运行 参与其中。
如果您的代码在命名模块中,并且您通过ClassLoader#getResource
访问资源,必须无条件打开该资源的包。否则,您将无法检索资源,即使资源在同一个模块中。
这与 Class#getResource
的行为不同 - 请注意区别。 Class#getResource
直截了当,没有这种令人讨厌的惊喜。
关于 getResource
的所有内容也适用于 getResourceAsStream
方法。
出于这个原因,我建议始终在 Class
上使用 getResource 方法,而不是在 ClassLoader
上使用 getResource 方法。
另请参阅我对 What is the difference between Class.getResource and ClassLoader.getResource
的回答