[英]Loading classes and resources in Java 9
我在 InfoQ 上阅读了这篇引用 Reinhold 的文章:
开发人员仍然可以使用 Java 9 中的 Java 类路径为 Java 运行时搜索类和资源文件。 只是有了 Java 9 的模块,开发人员不再需要类路径。
所以现在我的问题是:执行上面列出的任务的正确 Java 9 方法是什么? 您如何动态加载例如图像(没有摆弄相对路径)?
更有趣的是,如何检查一个类是否可用并动态做出决定(例如检查 Jackson 是否可用,如果可用,将它用于 JSON 序列化,如果不使用其他东西)?
文章还提到Spring Boot已经支持Java 9了,Spring Boot肯定做了很多动态加载。 所以也许有人知道我可以查看的 Spring 代码的价格?
首先,澄清一下,我既没有说也没有写上面引用的文字。 我永远不会那样说的。 这只是有关出版物的草率报道。
关于 Java 9 中的类加载和资源查找,最重要的一点是,它们从根本上没有改变。 您可以通过调用Class::forName
和Class
和ClassLoader
类中的各种getResource*
方法,以您一直拥有的相同方式搜索类和资源,无论您的代码是从类路径还是模块路径加载. 仍然有三个内置的类加载器,就像在 JDK 1.2 中一样,它们具有相同的委托关系。 因此,许多现有代码开箱即用。
有一些细微差别,如JEP 261 所述:内置类加载器的具体类型已更改,以前由引导类加载器加载的某些类现在由平台类加载器加载,以提高安全性。 因此,假定内置类加载器是URLClassLoader
或类由引导类加载器加载的现有代码可能需要稍作调整。
最后一个重要的区别是模块中的非类文件资源默认被封装,因此不能从模块外部定位,除非它们的有效包是open
。 要从自己的模块加载资源,最好使用Class
或Module
的资源查找方法,它们可以定位模块中的任何资源,而不是ClassLoader
那些,后者只能定位open
包中的非类文件资源的一个模块。
[编辑:这个答案是在马克的权威答案之前写的。 我已经修改了我的以提供一个简单的示例,可在 GitHub 上找到。]
根据此视频,Java 9 中的类加载保持不变。
例如,假设我们有:
net.codetojoy.example.resources
包中的图像的example.jar
net.codetojoy.example.Composer
是公开的(并在适用的情况下导出)App
类,它使用example.jar
作为库并尝试从中加载图像 App
的相关代码:
static InputStream getResourceAsStream(String resource)
throws Exception {
// Load net/codetojoy/example/resource/image.jpg
// Assume net.codetojoy.example.Composer is public/exported
// resource is 'resource/image.jpg'
InputStream result = Composer.class.getResourceAsStream(resource);
return result;
}
以下是JDK 9中example.jar
的几个案例:
老式的非模块化 Jar
如果example.jar
不是模块,则代码正常工作。 类加载不变。
带开放式包装的模块化罐
在本例中,这是module-info.java
文件:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// image is available
opens net.codetojoy.example.resources;
}
在这种情况下,图像可以由客户端加载,因为包是打开的。
不带开包的模块化罐
在这种情况下, module-info.java
是:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// package not opened: image not available
// opens net.codetojoy.example.resources;
}
在这种情况下,图像无法加载,因为强封装:模块通过不打开包装来保护图像。
GitHub 上的完整源代码。
除了现有的答案之外,我想举一个不同资源目录名称的非类文件资源的封装规则示例。
getResourceAsStream的规范说,如果包名从其名称派生,则该资源被封装。
因此,如果资源的目录名称不是有效的 Java 标识符,则不会对其进行封装。 这意味着如果一个模块有一个资源,例如,一个名为dir-1
的目录(包含一个无效字符-
在其名称中),它将始终可以从模块外部访问。
这是两个 Java 模块的示例( GitHub 中的源代码)。 模块 1包含以下资源文件:
├── dir-3
│ └── resource3.txt
├── dir1
│ └── resource1.txt
├── dir2
│ └── resource2.txt
└── root.txt
和module-info.java
:
module module_one {
opens dir1;
}
模块 2需要module-info.java
模块 1 :
module module_two {
requires module_one;
}
并有一个用于加载各种资源文件的示例主类:
package module2;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
loadResource("root.txt", "From module's root directory");
loadResource("dir1/resource1.txt", "From opened package `dir1`");
loadResource("dir2/resource2.txt", "From internal package `dir2`");
loadResource("dir-3/resource3.txt", "From directory `dir-3` with non-Java name");
}
public static void loadResource(String name, String comment) throws IOException {
// module2 application class loader
final var classLoader = Main.class.getClassLoader();
try (var in = classLoader.getResourceAsStream(name)) {
System.out.println();
System.out.println("// " + comment);
System.out.println(name + ": " + (in != null));
}
}
}
运行上面的类给出以下输出:
// From module's root directory
root.txt: true
// From opened package `dir1`
dir1/resource1.txt: true
// From internal package `dir2`
dir2/resource2.txt: false
// From directory `dir-3` with non-Java name
dir-3/resource3.txt: true
可以看到根目录下的资源文件和dir-3
目录下的资源文件没有被封装,因此模块2可以加载它们。
包dir1
被封装但无条件打开。 模块 2也可以加载它。
包dir2
是封装的,没有打开。 模块 2无法加载它。
请注意,模块 2不能在dir1
和dir2
目录下包含自己的资源,因为它们已经封装在模块 1 中。 如果您尝试添加dir1
您将收到以下错误:
Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package dir1 in both module module_one and module module_two
这里有一个Flyway 相关问题供参考。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.