47

我正在阅读InfoQ 上引用 Reinhold的这篇文章:

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

所以现在我的问题是:执行上述任务的正确 Java 9 方法是什么?您如何动态加载例如图像(不涉及相对路径)?

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

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

4

3 回答 3

93

首先,为了澄清事实,我既没有说也没有写上面引用的文字。我永远不会那样说。这只是有关出版物的草率报道。

关于 Java 9 中的类加载和资源查找,最重要的一点是,从根本上说,它们没有改变。无论您的代码是从类路径还是模块路径加载,您都可以像往常一样通过调用 和 类中Class::forName的各种getResource*方法来搜索类和资源。仍然有三个内置的类加载器,就像 JDK 1.2 中一样,它们具有相同的委托关系。因此,许多现有代码开箱即用。ClassClassLoader

有一些细微差别,如JEP 261中所述:内置类加载器的具体类型已更改,以前由引导类加载器加载的一些类现在由平台类加载器加载,以提高安全性。假设内置类加载器是 的现有代码URLClassLoader,或者类是由引导类加载器加载的,因此可能需要稍作调整。

最后一个重要的区别是模块中的非类文件资源是默认封装的,因此不能从模块外部定位,除非它们的有效包是 open. 要从您自己的模块加载资源,最好使用Classor中的资源查找方法Module,它可以找到您模块中的任何资源,而不是那些只能在模块的包中ClassLoader查找非类文件资源的方法。open

于 2017-07-18T17:50:07.443 回答
6

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

根据这个视频,Java 9 中的类加载没有改变。

例如,假设我们有:

  • 一个example.jar在包中包含图像的net.codetojoy.example.resources
  • 加强罐子,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;
}   

以下是example.jarJDK 9 中的一些情况:

老式的非模块化罐子

如果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 上的完整源代码。

于 2017-07-18T16:03:46.257 回答
4

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

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));
    }
  }
}

运行上面的类会得到以下输出:</p>

// 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 相关问题供参考。

于 2021-06-05T18:53:18.463 回答