正在加载 类 和 Java 中的资源 9
Loading classes and resources in Java 9
我正在阅读 this article on InfoQ 引用 Reinhold 的话:
Developers can still use the Java class path in Java 9 for the Java
runtime to search for classes and resource files. It's just that with
Java 9's modules, developers no longer need the class path.
所以现在我的问题是:Java9 执行上面列出的任务的正确方法是什么?你如何动态加载例如图像(缺少相对路径)?
更有趣的是,如何检查 class 是否可用并动态做出决定(例如检查 Jackson 是否可用,如果可用,将其用于 JSON 序列化,如果不使用其他东西)?
文章还提到 Spring Boot 已经支持 Java 9,并且 Spring Boot 确实做了很多动态加载。所以也许有人知道我可以查看的 Spring 中的代码片段?
[编辑:这个答案是在马克的权威答案之前写的。我修改了我的以提供一个简单的示例,可用 on GitHub.]
根据 this video,class 载入 Java 9 不变。
举个例子,假设我们有:
- 一个
example.jar
包含包中的图像 net.codetojoy.example.resources
- 为了加强罐子,
net.codetojoy.example.Composer
是 public(并在适用的情况下导出)
- 一个简单的
App
class,它使用 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
不是一个模块,代码就可以工作。 Class加载不变。
带有打开包装的模块化 Jar
在这种情况下,这是 module-info.java
文件:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// image is available
opens net.codetojoy.example.resources;
}
在这种情况下,客户端可以加载图像,因为包是打开的。
没有打开包装的模块化 Jar
在这种情况下,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;
}
这种情况下无法加载图片,因为强封装:模块在不开包的情况下保护图片。
完整来源 here on GitHub.
首先,澄清一下,我既没有说也没有写文字
上面引用。我永远不会那样说。那只是马虎
报告涉及的出版物部分。
了解class 加载和资源最重要的事情
在 Java 9 中查找是,在基本层面上,它们没有改变。
您可以像往常一样搜索 classes 和资源
有,通过调用 Class::forName
和各种 getResource*
方法
在 Class
和 ClassLoader
class 中,无论您的代码是否
从 class 路径或模块路径加载。还是三个
内置 class 加载器,就像 JDK 1.2 中一样,并且它们具有
相同的委托关系。因此,许多现有代码只是
开箱即用。
如 JEP 中所述,存在一些细微差别
261:具体类型
的内置 class 装载机已经改变,一些 classes 以前
由 bootstrap class 加载程序加载现在由平台 class 加载
加载程序以提高安全性。现有代码假设
内置 class 加载器是 URLClassLoader
,或者 class 由
bootstrap class 加载程序,因此可能需要进行细微调整。
最后一个重要区别是模块中的非 class 文件资源
默认情况下是封装的,因此 无法从外部定位
模块除非他们的有效包是
open
。
要从您自己的模块加载资源,最好使用
Class
或 Module
中的资源查找方法,可以定位任何
模块中的资源,而不是 ClassLoader
中的资源,这可以
仅在模块的 open
包中定位非 class 文件资源。
除了现有的答案,我想举一个例子,针对不同资源目录名称的非class文件资源的封装规则。
getResourceAsStream 的规范指出,如果 包名称 源自其名称,则资源被封装。
所以如果 resource’s directory name is NOT a valid Java identifier,它没有被封装。这意味着如果一个模块有一个资源位于,例如,一个名为 dir-1
的目录(其名称中包含一个无效字符 -
),它将始终可以从模块外部访问。
这里是两个 Java 模块 (source code in GitHub) 的示例。 模块 1 包含以下资源文件:
├── dir-3
│ └── resource3.txt
├── dir1
│ └── resource1.txt
├── dir2
│ └── resource2.txt
└── root.txt
和module-info.java
:
module module_one {
opens dir1;
}
模块 2 需要 模块 1 module-info.java
:
module module_two {
requires module_one;
}
并有一个示例主 class 用于加载各种资源文件:
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));
}
}
}
运行 上面的 class 给出以下输出:
// 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 related issue供参考
我正在阅读 this article on InfoQ 引用 Reinhold 的话:
Developers can still use the Java class path in Java 9 for the Java runtime to search for classes and resource files. It's just that with Java 9's modules, developers no longer need the class path.
所以现在我的问题是:Java9 执行上面列出的任务的正确方法是什么?你如何动态加载例如图像(缺少相对路径)?
更有趣的是,如何检查 class 是否可用并动态做出决定(例如检查 Jackson 是否可用,如果可用,将其用于 JSON 序列化,如果不使用其他东西)?
文章还提到 Spring Boot 已经支持 Java 9,并且 Spring Boot 确实做了很多动态加载。所以也许有人知道我可以查看的 Spring 中的代码片段?
[编辑:这个答案是在马克的权威答案之前写的。我修改了我的以提供一个简单的示例,可用 on GitHub.]
根据 this video,class 载入 Java 9 不变。
举个例子,假设我们有:
- 一个
example.jar
包含包中的图像net.codetojoy.example.resources
- 为了加强罐子,
net.codetojoy.example.Composer
是 public(并在适用的情况下导出) - 一个简单的
App
class,它使用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
不是一个模块,代码就可以工作。 Class加载不变。
带有打开包装的模块化 Jar
在这种情况下,这是 module-info.java
文件:
module net.codetojoy.example {
// export the Composer class
exports net.codetojoy.example;
// image is available
opens net.codetojoy.example.resources;
}
在这种情况下,客户端可以加载图像,因为包是打开的。
没有打开包装的模块化 Jar
在这种情况下,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;
}
这种情况下无法加载图片,因为强封装:模块在不开包的情况下保护图片。
完整来源 here on GitHub.
首先,澄清一下,我既没有说也没有写文字 上面引用。我永远不会那样说。那只是马虎 报告涉及的出版物部分。
了解class 加载和资源最重要的事情
在 Java 9 中查找是,在基本层面上,它们没有改变。
您可以像往常一样搜索 classes 和资源
有,通过调用 Class::forName
和各种 getResource*
方法
在 Class
和 ClassLoader
class 中,无论您的代码是否
从 class 路径或模块路径加载。还是三个
内置 class 加载器,就像 JDK 1.2 中一样,并且它们具有
相同的委托关系。因此,许多现有代码只是
开箱即用。
如 JEP 中所述,存在一些细微差别
261:具体类型
的内置 class 装载机已经改变,一些 classes 以前
由 bootstrap class 加载程序加载现在由平台 class 加载
加载程序以提高安全性。现有代码假设
内置 class 加载器是 URLClassLoader
,或者 class 由
bootstrap class 加载程序,因此可能需要进行细微调整。
最后一个重要区别是模块中的非 class 文件资源
默认情况下是封装的,因此 无法从外部定位
模块除非他们的有效包是
open
。
要从您自己的模块加载资源,最好使用
Class
或 Module
中的资源查找方法,可以定位任何
模块中的资源,而不是 ClassLoader
中的资源,这可以
仅在模块的 open
包中定位非 class 文件资源。
除了现有的答案,我想举一个例子,针对不同资源目录名称的非class文件资源的封装规则。
getResourceAsStream 的规范指出,如果 包名称 源自其名称,则资源被封装。
所以如果 resource’s directory name is NOT a valid Java identifier,它没有被封装。这意味着如果一个模块有一个资源位于,例如,一个名为 dir-1
的目录(其名称中包含一个无效字符 -
),它将始终可以从模块外部访问。
这里是两个 Java 模块 (source code in GitHub) 的示例。 模块 1 包含以下资源文件:
├── dir-3
│ └── resource3.txt
├── dir1
│ └── resource1.txt
├── dir2
│ └── resource2.txt
└── root.txt
和module-info.java
:
module module_one {
opens dir1;
}
模块 2 需要 模块 1 module-info.java
:
module module_two {
requires module_one;
}
并有一个示例主 class 用于加载各种资源文件:
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));
}
}
}
运行 上面的 class 给出以下输出:
// 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 related issue供参考