在 Java 9 之后加载类和资源

Loading classes and resources post Java 9

提问人:kaqqao 提问时间:7/18/2017 最后编辑:kaqqao 更新时间:8/7/2023 访问量:14338

问:

我在 InfoQ 上读到这篇文章,引用了 Reinhold 的话:

开发人员仍然可以在 Java 9 中使用 Java 类路径来执行 Java 运行时来搜索类和资源文件。只是 Java 9 的模块,开发人员不再需要类路径。

所以现在我的问题是:执行上面列出的任务的正确 Java 9 方法是什么?您如何动态加载图像(无需摆弄相对路径)?

更有趣的是,如何检查一个类是否可用并动态地做出决定(例如,检查 Jackson 是否可用,如果可用,则将其用于 JSON 序列化,如果没有,请使用其他方法)?

文章还提到 Spring Boot 已经支持 Java 9,Spring Boot 肯定做了很多动态加载。所以也许有人知道我可以查看的 Spring 代码的很多内容?

java spring-boot java-9 java-platform-module-system 模块路径

评论


答:

6赞 Michael Easter 7/19/2017 #1

[编辑:这个答案是在马克的权威答案之前写的。我已经修改了我的版本,以提供一个简单的示例,可在 GitHub 上找到。

根据此视频,Java 9 中的类加载保持不变。

举个例子,假设我们有:

  • 在包中包含映像的example.jarnet.codetojoy.example.resources
  • 加强罐子,是公开的(并在适用的情况下出口)net.codetojoy.example.Composer
  • 一个简单的类,用作库并尝试从中加载图像Appexample.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

老式、非模块化的罐子

如果不是模块,则代码可以正常工作。类加载保持不变。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 上的完整源代码。

评论

0赞 tresf 9/29/2017
谢谢,但是那些使用不支持的旧语言级别的人呢,但我们仍然在资源加载器方面遇到障碍?对于希望在尚未采用新的 JDK9 语言级别的情况下很好地使用 JRE9 的开发人员来说,有什么让步吗?module-info.java
1赞 tresf 9/29/2017
没关系,我的问题是 versus 的使用 .JDK9 似乎与 JDK8、JDK7 处理前者的方式不同。Foo.class.getClass().getResourceAsStream(...)Foo.class.getResourceAsStream(...)
97赞 Mark Reinhold 7/19/2017 #2

首先,为了澄清事实,我既没有说也没有写文字 上面引用。我永远不会这么说。这太草率了 报告所涉出版物的部分。

关于类加载和资源,需要了解的最重要的事情 Java 9 中的查找是,从根本上讲,它们没有改变。 您可以像往常一样搜索类和资源 have,通过调用和各种方法 在 和 类中,无论您的代码是否 从类路径或模块路径加载。还有三个 内置类加载器,就像 JDK 1.2 中一样,它们具有 相同的委派关系。因此,许多现有代码只是 开箱即用。Class::forNamegetResource*ClassClassLoader

正如 JEP 中指出的那样,有一些细微差别 261:混凝土类型 的内置类加载器已更改,并且以前的某些类 由 Bootstrap 类装入器加载现在由 Platform 类加载 loader 以提高安全性。现有代码,假设 内置类加载器是一个 ,或者一个类是由 因此,Bootstrap 类装入器可能需要稍作调整。URLClassLoader

最后一个重要区别是模块中的非类文件资源 默认情况下是封装的,因此无法从外部定位 该模块,除非其有效包已打开。 要从您自己的模块加载资源,最好使用 或 中的 resource-lookup 方法,它可以找到任何 资源,而不是 中的资源,这可以 仅在模块的包中查找非类文件资源。ClassModuleClassLoaderopen

4赞 Alexey Gavrilov 6/6/2021 #3

除了现有的答案之外,我还想举例说明不同资源目录名称的非类文件资源的封装规则。

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 需要模块 1module-info.java

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

正如你所看到的,根目录和目录中的资源文件没有被封装,因此模块 2 可以加载它们。dir-3

包装是封装的,但无条件打开。模块 2 也可以加载它。dir1

包装是封装的,没有打开。模块 2 无法加载它。dir2

请注意,模块 2 不能在 和 目录下包含它们自己的资源,因为它们已经封装在模块 1 中。如果尝试添加,将出现以下错误:dir1dir2dir1

Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package dir1 in both module module_one and module module_two

这是一个与Flyway相关的问题,供参考。

评论

0赞 Thomas 2/11/2023
我不能同意,我不是模块系统的专家,但是当不使用这个模块A的类加载器时,我无法从moduleA加载任何资源。即使在限定的标识符中,它也是不可见的。