正在加载 类 和 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* 方法 在 ClassClassLoader class 中,无论您的代码是否 从 class 路径或模块路径加载。还是三个 内置 class 加载器,就像 JDK 1.2 中一样,并且它们具有 相同的委托关系。因此,许多现有代码只是 开箱即用。

JEP 中所述,存在一些细微差别 261:具体类型 的内置 class 装载机已经改变,一些 classes 以前 由 bootstrap class 加载程序加载现在由平台 class 加载 加载程序以提高安全性。现有代码假设 内置 class 加载器是 URLClassLoader,或者 class 由 bootstrap class 加载程序,因此可能需要进行细微调整。

最后一个重要区别是模块中的非 class 文件资源 默认情况下是封装的,因此 无法从外部定位 模块除非他们的有效包是 open。 要从您自己的模块加载资源,最好使用 ClassModule 中的资源查找方法,可以定位任何 模块中的资源,而不是 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 不能在 dir1dir2 目录下包含它们自己的资源,因为它们已经封装在 模块 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供参考